@tendrilapp/cli 0.1.33 → 0.1.34

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.
Files changed (2) hide show
  1. package/dist/tendril.js +1402 -643
  2. package/package.json +1 -1
package/dist/tendril.js CHANGED
@@ -449,7 +449,7 @@ function parseVariantAxes(nodeName) {
449
449
  if (!nodeName.includes("=")) return void 0;
450
450
  const axes = {};
451
451
  for (const pair of nodeName.split(",")) {
452
- const [key, value] = pair.split("=").map((s) => s.trim());
452
+ const [key, value] = pair.split("=").map((s) => decodeXmlEntities(s.trim()));
453
453
  if (key !== void 0 && key !== "" && value !== void 0 && value !== "") {
454
454
  axes[key] = [value];
455
455
  }
@@ -785,7 +785,7 @@ function resolveAxisDefaultWithRule(domain, recorded, override) {
785
785
  function resolveAxisDefault(domain, recorded, override) {
786
786
  return resolveAxisDefaultWithRule(domain, recorded, override).value;
787
787
  }
788
- var INTERACTION_STATES, BOOLEAN_STATES, ENGAGED_STATES, INTERACTION_EVIDENCE_VALUES, kebab, OFF_VALUES, ON_VALUES;
788
+ var INTERACTION_STATES, BOOLEAN_STATES, ENGAGED_STATES, INTERACTION_EVIDENCE_VALUES, ENGAGED_STATE_VALUES, INTERACTION_TREATMENT_VALUES, kebab, OFF_VALUES, ON_VALUES;
789
789
  var init_axis_defaults = __esm({
790
790
  "packages/figma/src/recording/axis-defaults.ts"() {
791
791
  "use strict";
@@ -793,6 +793,32 @@ var init_axis_defaults = __esm({
793
793
  BOOLEAN_STATES = /* @__PURE__ */ new Set(["disabled", "loading"]);
794
794
  ENGAGED_STATES = /* @__PURE__ */ new Set(["selected", "checked", "indeterminate", "mixed", "open", "expanded"]);
795
795
  INTERACTION_EVIDENCE_VALUES = /* @__PURE__ */ new Set([
796
+ ...INTERACTION_STATES,
797
+ ...BOOLEAN_STATES,
798
+ "hovered",
799
+ "focused",
800
+ "press",
801
+ "selected",
802
+ "checked",
803
+ "unselected",
804
+ "unchecked",
805
+ "indeterminate",
806
+ // Cycle B C4: engaged/typing evidence. A dropdown's open pose and an
807
+ // input's typing pose PROVE the control is interactive — the sweep
808
+ // measured "the recording proves no interactive poses" printed for
809
+ // dropdowns and inputs (the trust anchor calling a control "not a
810
+ // control"). DELIBERATE VERDICT CHANGE, release-noted: a set with
811
+ // these poses and ZERO verified interaction behaviors now honestly
812
+ // fails the interaction-evidence gate instead of passing silently.
813
+ "open",
814
+ "expanded",
815
+ "typing",
816
+ "on",
817
+ "off",
818
+ "filled"
819
+ ]);
820
+ ENGAGED_STATE_VALUES = /* @__PURE__ */ new Set(["open", "expanded", "typing", "on", "off", "filled"]);
821
+ INTERACTION_TREATMENT_VALUES = /* @__PURE__ */ new Set([
796
822
  ...INTERACTION_STATES,
797
823
  ...BOOLEAN_STATES,
798
824
  "hovered",
@@ -1326,15 +1352,81 @@ var init_session = __esm({
1326
1352
  }
1327
1353
  });
1328
1354
 
1355
+ // packages/figma/src/recording/visibility.ts
1356
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1357
+ import path2 from "node:path";
1358
+ function effectivelyInvisible(node) {
1359
+ if (node.hidden === true) return true;
1360
+ if (node.width === 0 || node.height === 0) return true;
1361
+ return false;
1362
+ }
1363
+ function repVisibility(setDir, slug) {
1364
+ const file = path2.join(setDir, slug, "get_metadata.json");
1365
+ if (!existsSync2(file)) return void 0;
1366
+ let roots;
1367
+ try {
1368
+ roots = parseMetadataForest(envelopeTextContent(JSON.parse(readFileSync2(file, "utf8")))).roots;
1369
+ } catch {
1370
+ return void 0;
1371
+ }
1372
+ const byId = /* @__PURE__ */ new Map();
1373
+ const byName = /* @__PURE__ */ new Map();
1374
+ const hiddenIds = /* @__PURE__ */ new Set();
1375
+ const walk2 = (n, underHidden) => {
1376
+ const invisible = underHidden || effectivelyInvisible(n);
1377
+ byId.set(n.id, !invisible);
1378
+ if (invisible) hiddenIds.add(n.id);
1379
+ const norm2 = normalizedLayerName(n.name);
1380
+ if (norm2 !== "") byName.set(norm2, (byName.get(norm2) ?? false) || !invisible);
1381
+ for (const c of n.children) walk2(c, invisible);
1382
+ };
1383
+ for (const rootNode of roots) walk2(rootNode, false);
1384
+ return { byId, byName, hiddenIds };
1385
+ }
1386
+ function poseVisibility(setDir, repSlugs, match) {
1387
+ const presentIn = [];
1388
+ const visibleIn = [];
1389
+ const norm2 = match.name === void 0 ? void 0 : normalizedLayerName(match.name);
1390
+ for (const slug of repSlugs) {
1391
+ const rep = repVisibility(setDir, slug);
1392
+ if (rep === void 0) continue;
1393
+ let present = false;
1394
+ let visible = false;
1395
+ if (match.nodeId !== void 0 && rep.byId.has(match.nodeId)) {
1396
+ present = true;
1397
+ visible = rep.byId.get(match.nodeId) === true;
1398
+ }
1399
+ if (!present && norm2 !== void 0 && rep.byName.has(norm2)) {
1400
+ present = true;
1401
+ visible = rep.byName.get(norm2) === true;
1402
+ }
1403
+ if (present) {
1404
+ presentIn.push(slug);
1405
+ if (visible) visibleIn.push(slug);
1406
+ }
1407
+ }
1408
+ return { presentIn, visibleIn };
1409
+ }
1410
+ var normalizedLayerName, invisibleInEveryPose;
1411
+ var init_visibility = __esm({
1412
+ "packages/figma/src/recording/visibility.ts"() {
1413
+ "use strict";
1414
+ init_normalize();
1415
+ init_session();
1416
+ normalizedLayerName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
1417
+ invisibleInEveryPose = (v) => v.presentIn.length > 0 && v.visibleIn.length === 0;
1418
+ }
1419
+ });
1420
+
1329
1421
  // packages/figma/src/recording/compose.ts
1330
1422
  import { createHash } from "node:crypto";
1331
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
1332
- import path2 from "node:path";
1423
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
1424
+ import path3 from "node:path";
1333
1425
  import { z as z5 } from "zod";
1334
1426
  function buildComposeIndex(roots, depth = 3) {
1335
1427
  const found = [];
1336
1428
  const walk2 = (dir, remaining) => {
1337
- if (existsSync2(path2.join(dir, "recording-set.json"))) {
1429
+ if (existsSync3(path3.join(dir, "recording-set.json"))) {
1338
1430
  found.push(dir);
1339
1431
  return;
1340
1432
  }
@@ -1347,17 +1439,17 @@ function buildComposeIndex(roots, depth = 3) {
1347
1439
  }
1348
1440
  for (const e of entries2) {
1349
1441
  if (e === "node_modules" || e.startsWith(".")) continue;
1350
- const full = path2.join(dir, e);
1442
+ const full = path3.join(dir, e);
1351
1443
  try {
1352
1444
  if (statSync(full).isDirectory()) walk2(full, remaining - 1);
1353
1445
  } catch {
1354
1446
  }
1355
1447
  }
1356
1448
  };
1357
- for (const r of roots) walk2(path2.resolve(r), depth);
1449
+ for (const r of roots) walk2(path3.resolve(r), depth);
1358
1450
  const seen = /* @__PURE__ */ new Set();
1359
1451
  const uniqueDirs = found.filter((d) => {
1360
- const key = path2.resolve(d);
1452
+ const key = path3.resolve(d);
1361
1453
  if (seen.has(key)) return false;
1362
1454
  seen.add(key);
1363
1455
  return true;
@@ -1377,10 +1469,10 @@ function buildComposeIndex(roots, depth = 3) {
1377
1469
  for (const rep of manifest.reps) {
1378
1470
  variantNodeIds.add(rep.nodeId);
1379
1471
  repSlugByVariantNode.set(rep.nodeId, rep.slug);
1380
- const metaFile = path2.join(dir, rep.slug, "get_metadata.json");
1381
- if (!existsSync2(metaFile)) continue;
1472
+ const metaFile = path3.join(dir, rep.slug, "get_metadata.json");
1473
+ if (!existsSync3(metaFile)) continue;
1382
1474
  try {
1383
- const text = envelopeTextContent(JSON.parse(readFileSync2(metaFile, "utf8")));
1475
+ const text = envelopeTextContent(JSON.parse(readFileSync3(metaFile, "utf8")));
1384
1476
  const ids = /* @__PURE__ */ new Set();
1385
1477
  const collect = (n) => {
1386
1478
  if (n.id !== "") ids.add(n.id);
@@ -1413,11 +1505,11 @@ function sameComponent(a, b) {
1413
1505
  return false;
1414
1506
  }
1415
1507
  function emissionTails(setDir, repSlug) {
1416
- const file = path2.join(setDir, repSlug, "get_design_context.json");
1417
- if (!existsSync2(file)) return /* @__PURE__ */ new Map();
1508
+ const file = path3.join(setDir, repSlug, "get_design_context.json");
1509
+ if (!existsSync3(file)) return /* @__PURE__ */ new Map();
1418
1510
  let text;
1419
1511
  try {
1420
- text = envelopeTextContent(JSON.parse(readFileSync2(file, "utf8")));
1512
+ text = envelopeTextContent(JSON.parse(readFileSync3(file, "utf8")));
1421
1513
  } catch {
1422
1514
  return /* @__PURE__ */ new Map();
1423
1515
  }
@@ -1437,16 +1529,20 @@ function emissionTails(setDir, repSlug) {
1437
1529
  return byHead;
1438
1530
  }
1439
1531
  function hostInstances(setDir, repSlug) {
1440
- const metaFile = path2.join(setDir, repSlug, "get_metadata.json");
1441
- if (!existsSync2(metaFile)) return [];
1532
+ const metaFile = path3.join(setDir, repSlug, "get_metadata.json");
1533
+ if (!existsSync3(metaFile)) return [];
1442
1534
  try {
1443
- const text = envelopeTextContent(JSON.parse(readFileSync2(metaFile, "utf8")));
1535
+ const text = envelopeTextContent(JSON.parse(readFileSync3(metaFile, "utf8")));
1444
1536
  const out = [];
1445
- const walk2 = (n) => {
1446
- if (n.type === "INSTANCE") out.push({ id: n.id, name: n.name });
1447
- for (const c of n.children) walk2(c);
1537
+ const walkRoot = (root) => {
1538
+ const walk2 = (n, underHidden) => {
1539
+ const invisible = underHidden || effectivelyInvisible(n);
1540
+ if (n.type === "INSTANCE") out.push({ id: n.id, name: n.name, visible: !invisible });
1541
+ for (const c of n.children) walk2(c, invisible);
1542
+ };
1543
+ walk2(root, false);
1448
1544
  };
1449
- for (const root of parseMetadataForest(text).roots) walk2(root);
1545
+ for (const root of parseMetadataForest(text).roots) walkRoot(root);
1450
1546
  return out;
1451
1547
  } catch {
1452
1548
  return [];
@@ -1455,12 +1551,32 @@ function hostInstances(setDir, repSlug) {
1455
1551
  function composeReport(index) {
1456
1552
  const edges = [];
1457
1553
  for (const host of index) {
1554
+ const visibleEver = /* @__PURE__ */ new Map();
1555
+ for (const [, repSlug] of host.repSlugByVariantNode) {
1556
+ for (const inst of hostInstances(host.dir, repSlug)) {
1557
+ visibleEver.set(inst.id, (visibleEver.get(inst.id) ?? false) || inst.visible);
1558
+ }
1559
+ }
1458
1560
  for (const [variantNodeId, slug] of host.repSlugByVariantNode) {
1459
1561
  void variantNodeId;
1460
1562
  const instances = hostInstances(host.dir, slug);
1461
1563
  if (instances.length === 0) continue;
1462
1564
  const tailsByHead = emissionTails(host.dir, slug);
1463
1565
  for (const inst of instances) {
1566
+ if (visibleEver.get(inst.id) !== true) {
1567
+ edges.push({
1568
+ hostSet: host.dir,
1569
+ hostRep: slug,
1570
+ instanceId: inst.id,
1571
+ instanceName: inst.name,
1572
+ kind: "hidden",
1573
+ partners: [],
1574
+ disclosures: [
1575
+ `HIDDEN in every recorded pose \u2014 the designer's off-switch is unambiguous, so no composition is proposed (obligations require visible recorded truth). If this region should compose, add a pose that SHOWS the instance and re-record.`
1576
+ ]
1577
+ });
1578
+ continue;
1579
+ }
1464
1580
  const tails = tailsByHead.get(inst.id) ?? /* @__PURE__ */ new Map();
1465
1581
  const disclosures = [];
1466
1582
  const refused = [];
@@ -1488,7 +1604,7 @@ function composeReport(index) {
1488
1604
  }
1489
1605
  const eligible = [];
1490
1606
  for (const g of groups) {
1491
- const uncaptured = [host.figmaFile === void 0 ? "the host set" : void 0, ...g.map((m) => m.figmaFile === void 0 ? path2.basename(m.dir) : void 0)].filter(
1607
+ const uncaptured = [host.figmaFile === void 0 ? "the host set" : void 0, ...g.map((m) => m.figmaFile === void 0 ? path3.basename(m.dir) : void 0)].filter(
1492
1608
  (x) => x !== void 0
1493
1609
  );
1494
1610
  if (uncaptured.length > 0) {
@@ -1600,11 +1716,11 @@ function composeReport(index) {
1600
1716
  return edges;
1601
1717
  }
1602
1718
  function confirmedCompositionStatus(hostSet) {
1603
- const manifestFile = path2.join(hostSet, "recording-set.json");
1604
- if (!existsSync2(manifestFile)) return { rows: [] };
1719
+ const manifestFile = path3.join(hostSet, "recording-set.json");
1720
+ if (!existsSync3(manifestFile)) return { rows: [] };
1605
1721
  let rawEntries;
1606
1722
  try {
1607
- const parsed = JSON.parse(readFileSync2(manifestFile, "utf8"));
1723
+ const parsed = JSON.parse(readFileSync3(manifestFile, "utf8"));
1608
1724
  rawEntries = Array.isArray(parsed["compositions"]) ? parsed["compositions"] : [];
1609
1725
  } catch {
1610
1726
  return { rows: [], malformed: "the recording-set manifest is not readable JSON" };
@@ -1623,11 +1739,11 @@ function confirmedCompositionStatus(hostSet) {
1623
1739
  const rows = [];
1624
1740
  for (const entry of confirmed) {
1625
1741
  const partnerRels = Object.keys(entry.partner.manifestSha256).map(fromStoredRel);
1626
- const partnerDirs = partnerRels.map((rel) => path2.resolve(hostSet, rel));
1742
+ const partnerDirs = partnerRels.map((rel) => path3.resolve(hostSet, rel));
1627
1743
  const entryKey = fromStoredRel(entry.partner.key);
1628
1744
  const affectedReps = [...new Set(entry.instances.map((i) => i.hostRep))];
1629
1745
  const remediation = `re-run \`tendril compose --set ${hostSet}\` after repairing`;
1630
- const missing = partnerDirs.filter((d) => !existsSync2(path2.join(d, "recording-set.json")));
1746
+ const missing = partnerDirs.filter((d) => !existsSync3(path3.join(d, "recording-set.json")));
1631
1747
  if (missing.length > 0) {
1632
1748
  rows.push({
1633
1749
  key: entryKey,
@@ -1635,17 +1751,17 @@ function confirmedCompositionStatus(hostSet) {
1635
1751
  status: "partner-missing",
1636
1752
  instances: entry.instances,
1637
1753
  affectedReps,
1638
- detail: `confirmed partner set(s) not found: ${missing.map((d) => toPosixRel(path2.relative(hostSet, d))).join(", ")} \u2014 the decision names recordings that are not there; ${remediation}`
1754
+ detail: `confirmed partner set(s) not found: ${missing.map((d) => toPosixRel(path3.relative(hostSet, d))).join(", ")} \u2014 the decision names recordings that are not there; ${remediation}`
1639
1755
  });
1640
1756
  continue;
1641
1757
  }
1642
1758
  let stale = false;
1643
1759
  let unreadable;
1644
1760
  for (const rel of partnerRels) {
1645
- const file = path2.join(path2.resolve(hostSet, rel), "recording-set.json");
1761
+ const file = path3.join(path3.resolve(hostSet, rel), "recording-set.json");
1646
1762
  try {
1647
- const bytes = readFileSync2(file);
1648
- loadManifest(path2.resolve(hostSet, rel));
1763
+ const bytes = readFileSync3(file);
1764
+ loadManifest(path3.resolve(hostSet, rel));
1649
1765
  const storedSha = entry.partner.manifestSha256[Object.keys(entry.partner.manifestSha256).find((k) => fromStoredRel(k) === rel)];
1650
1766
  if (createHashHex(bytes) !== storedSha) stale = true;
1651
1767
  } catch (err) {
@@ -1667,8 +1783,8 @@ function confirmedCompositionStatus(hostSet) {
1667
1783
  const edges = composeReport(buildComposeIndex([hostSet, ...partnerDirs]));
1668
1784
  const supportedInstances = /* @__PURE__ */ new Map();
1669
1785
  for (const e of edges) {
1670
- if (e.hostSet !== path2.resolve(hostSet) || e.kind !== "substitution" || e.pose === void 0) continue;
1671
- if (pairKeyFor(path2.resolve(hostSet), e.partners.map((p) => p.dir)) !== entryKey) continue;
1786
+ if (e.hostSet !== path3.resolve(hostSet) || e.kind !== "substitution" || e.pose === void 0) continue;
1787
+ if (pairKeyFor(path3.resolve(hostSet), e.partners.map((p) => p.dir)) !== entryKey) continue;
1672
1788
  supportedInstances.set(`${e.hostRep}\0${e.instanceId}`, e.pose.variantNodeId);
1673
1789
  }
1674
1790
  const unsupported = entry.instances.filter((i) => supportedInstances.get(`${i.hostRep}\0${i.instanceId}`) !== i.poseVariantNodeId);
@@ -1679,7 +1795,7 @@ function confirmedCompositionStatus(hostSet) {
1679
1795
  status: ok ? stale ? "stale-supported" : "supported" : stale ? "stale-unsupported" : "unsupported",
1680
1796
  instances: entry.instances,
1681
1797
  affectedReps: ok ? [] : [...new Set(unsupported.map((i) => i.hostRep))],
1682
- detail: ok ? stale ? "the partner manifest changed since the decision (pinned bytes differ) \u2014 the CURRENT recordings still support every confirmed edge" : "the recordings support every confirmed edge (identity, rep attribution and pose re-derived)" : `${unsupported.length} confirmed instance(s) are NOT supported by the current recordings (${unsupported.map((i) => `${i.hostRep}/${i.instanceId}`).join(", ")}) \u2014 a confirmed composition the evidence does not derive; ${remediation}`
1798
+ detail: ok ? stale ? "the partner manifest changed since the decision (pinned bytes differ) \u2014 the CURRENT recordings still support every confirmed edge" : "the recordings support every confirmed edge (identity, rep attribution and pose re-derived)" : `${unsupported.length} confirmed instance(s) are NOT supported by the current recordings (${unsupported.map((i) => `${i.hostRep}/${i.instanceId}`).join(", ")}) \u2014 a confirmed composition the evidence does not derive; ${remediation}. If the instance is HIDDEN in every recorded pose, this confirmation predates the visibility principle (Cycle A) and re-running compose cannot re-open it (asked-once): either re-record a pose that SHOWS the instance, or remove this entry from the manifest's compositions array by hand \u2014 a retire flag is a named follow-up`
1683
1799
  });
1684
1800
  }
1685
1801
  return { rows, ...malformedEntries.length > 0 ? { malformed: malformedEntries.join("; ") } : {} };
@@ -1693,6 +1809,7 @@ var init_compose = __esm({
1693
1809
  "use strict";
1694
1810
  init_session();
1695
1811
  init_normalize();
1812
+ init_visibility();
1696
1813
  CompositionEntrySchema = z5.object({
1697
1814
  v: z5.literal(1),
1698
1815
  partner: z5.object({
@@ -1706,39 +1823,53 @@ var init_compose = __esm({
1706
1823
  });
1707
1824
  norm = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
1708
1825
  FOOTER = /Node ids have been added to the code as data attributes/i;
1709
- kitLabel = (e) => `${e.displayName} [${path2.basename(e.dir)}${e.figmaFile !== void 0 ? `, file ${e.figmaFile}` : ", file identity NOT captured"}]`;
1710
- toPosixRel = (rel) => rel.split(path2.sep).join("/");
1826
+ kitLabel = (e) => `${e.displayName} [${path3.basename(e.dir)}${e.figmaFile !== void 0 ? `, file ${e.figmaFile}` : ", file identity NOT captured"}]`;
1827
+ toPosixRel = (rel) => rel.split(path3.sep).join("/");
1711
1828
  fromStoredRel = (rel) => rel.replace(/\\/g, "/");
1712
- pairKeyFor = (hostSet, dirs) => dirs.map((d) => toPosixRel(path2.relative(hostSet, d))).sort().join("+");
1829
+ pairKeyFor = (hostSet, dirs) => dirs.map((d) => toPosixRel(path3.relative(hostSet, d))).sort().join("+");
1713
1830
  }
1714
1831
  });
1715
1832
 
1716
1833
  // packages/figma/src/recording/roles.ts
1717
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
1718
- import path3 from "node:path";
1834
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
1835
+ import path4 from "node:path";
1719
1836
  function emissionIdSets(emission) {
1720
1837
  const own = /* @__PURE__ */ new Set();
1721
1838
  const referenced = /* @__PURE__ */ new Set();
1839
+ const tailsByHead = /* @__PURE__ */ new Map();
1722
1840
  for (const m of emission.matchAll(NODE_ID_RE)) {
1723
1841
  const id = m[1];
1724
1842
  if (id.startsWith("I")) {
1725
1843
  const segments = id.slice(1).split(";");
1726
1844
  const head = segments[0];
1727
- if (head !== void 0) own.add(head);
1845
+ if (head !== void 0) {
1846
+ own.add(head);
1847
+ const tails = tailsByHead.get(head) ?? tailsByHead.set(head, /* @__PURE__ */ new Set()).get(head);
1848
+ for (const seg of segments.slice(1)) tails.add(seg);
1849
+ }
1728
1850
  for (const seg of segments.slice(1)) referenced.add(seg);
1729
1851
  } else {
1730
1852
  own.add(id);
1731
1853
  }
1732
1854
  }
1733
- return { own, referenced };
1855
+ return { own, referenced, tailsByHead };
1734
1856
  }
1735
1857
  function deriveRoles(setDir, manifest) {
1736
1858
  const m = manifest ?? loadManifest(setDir);
1737
- const reps = m.reps.filter((r) => existsSync3(path3.join(setDir, r.slug, "get_design_context.json")));
1859
+ const reps = m.reps.filter((r) => existsSync4(path4.join(setDir, r.slug, "get_design_context.json")));
1738
1860
  const sets = /* @__PURE__ */ new Map();
1739
1861
  for (const rep of reps) {
1740
- const env = JSON.parse(readFileSync3(path3.join(setDir, rep.slug, "get_design_context.json"), "utf8"));
1862
+ const env = JSON.parse(readFileSync4(path4.join(setDir, rep.slug, "get_design_context.json"), "utf8"));
1741
1863
  const ids = emissionIdSets(env.content.map((c) => c.text ?? "").join("\n"));
1864
+ const hiddenHeads = repVisibility(setDir, rep.slug)?.hiddenIds;
1865
+ if (hiddenHeads !== void 0) {
1866
+ const visibleTails = /* @__PURE__ */ new Set();
1867
+ for (const [head, tails] of ids.tailsByHead) if (!hiddenHeads.has(head)) for (const t of tails) visibleTails.add(t);
1868
+ for (const [head, tails] of ids.tailsByHead) {
1869
+ if (!hiddenHeads.has(head)) continue;
1870
+ for (const t of tails) if (!visibleTails.has(t)) ids.referenced.delete(t);
1871
+ }
1872
+ }
1742
1873
  ids.own.add(rep.nodeId);
1743
1874
  sets.set(rep.slug, ids);
1744
1875
  }
@@ -1785,6 +1916,7 @@ var init_roles = __esm({
1785
1916
  "packages/figma/src/recording/roles.ts"() {
1786
1917
  "use strict";
1787
1918
  init_session();
1919
+ init_visibility();
1788
1920
  NODE_ID_RE = /data-node-id="([^"]+)"/g;
1789
1921
  }
1790
1922
  });
@@ -1806,6 +1938,7 @@ var init_src = __esm({
1806
1938
  init_axis_defaults();
1807
1939
  init_plan();
1808
1940
  init_compose();
1941
+ init_visibility();
1809
1942
  init_session();
1810
1943
  init_envelope_content();
1811
1944
  init_roles();
@@ -1816,8 +1949,8 @@ var init_src = __esm({
1816
1949
  function variableNameToPath(name) {
1817
1950
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1818
1951
  }
1819
- function tokenPathToCssVar(path45) {
1820
- return `--${path45.join("-")}`;
1952
+ function tokenPathToCssVar(path46) {
1953
+ return `--${path46.join("-")}`;
1821
1954
  }
1822
1955
  function toDtcgToken(variable, defaultMode) {
1823
1956
  const modes = Object.keys(variable.valuesByMode);
@@ -1861,11 +1994,11 @@ function toDtcgToken(variable, defaultMode) {
1861
1994
  }
1862
1995
  function mapVariablesToDtcg(variables, defaultMode = "light") {
1863
1996
  const entries = variables.map((variable) => {
1864
- const path45 = variableNameToPath(variable.name);
1865
- if (path45.length === 0) {
1997
+ const path46 = variableNameToPath(variable.name);
1998
+ if (path46.length === 0) {
1866
1999
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
1867
2000
  }
1868
- return { variable, path: path45 };
2001
+ return { variable, path: path46 };
1869
2002
  });
1870
2003
  const groupPrefixes = /* @__PURE__ */ new Set();
1871
2004
  for (const e of entries) {
@@ -1886,21 +2019,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
1886
2019
  }
1887
2020
  const tokens = {};
1888
2021
  const flat = [];
1889
- for (const { variable, path: path45 } of entries) {
2022
+ for (const { variable, path: path46 } of entries) {
1890
2023
  const token = toDtcgToken(variable, defaultMode);
1891
2024
  let group = tokens;
1892
- for (const segment of path45.slice(0, -1)) {
2025
+ for (const segment of path46.slice(0, -1)) {
1893
2026
  const existing = group[segment];
1894
2027
  group = existing ?? (group[segment] = {});
1895
2028
  }
1896
- const leaf = path45[path45.length - 1];
2029
+ const leaf = path46[path46.length - 1];
1897
2030
  if (group[leaf] !== void 0) {
1898
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path45.join(".")}" (variable ${variable.id})`);
2031
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path46.join(".")}" (variable ${variable.id})`);
1899
2032
  }
1900
2033
  group[leaf] = token;
1901
2034
  flat.push({
1902
- path: path45.join("."),
1903
- cssVar: tokenPathToCssVar(path45),
2035
+ path: path46.join("."),
2036
+ cssVar: tokenPathToCssVar(path46),
1904
2037
  type: token.$type,
1905
2038
  value: token.$value
1906
2039
  });
@@ -2089,9 +2222,9 @@ function boundId(value) {
2089
2222
  return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
2090
2223
  }
2091
2224
  function resolveBinding(ctx, id) {
2092
- const path45 = ctx.pathById.get(id);
2093
- if (path45 === void 0) ctx.unresolved.add(id);
2094
- return path45;
2225
+ const path46 = ctx.pathById.get(id);
2226
+ if (path46 === void 0) ctx.unresolved.add(id);
2227
+ return path46;
2095
2228
  }
2096
2229
  function parseVariantProps(name) {
2097
2230
  if (!name.includes("=")) return void 0;
@@ -2126,8 +2259,8 @@ function walk(ctx, raw) {
2126
2259
  if (!isObject(paint) || paint["visible"] === false) continue;
2127
2260
  const id = boundId(paint);
2128
2261
  if (id !== void 0) {
2129
- const path45 = resolveBinding(ctx, id);
2130
- if (path45 !== void 0) tokens.add(path45);
2262
+ const path46 = resolveBinding(ctx, id);
2263
+ if (path46 !== void 0) tokens.add(path46);
2131
2264
  } else if (typeof paint["color"] === "string") {
2132
2265
  ctx.hardcoded.push({ node: name, property, value: paint["color"] });
2133
2266
  }
@@ -2135,8 +2268,8 @@ function walk(ctx, raw) {
2135
2268
  }
2136
2269
  const radiusId = boundId(raw["cornerRadius"]);
2137
2270
  if (radiusId !== void 0) {
2138
- const path45 = resolveBinding(ctx, radiusId);
2139
- if (path45 !== void 0) tokens.add(path45);
2271
+ const path46 = resolveBinding(ctx, radiusId);
2272
+ if (path46 !== void 0) tokens.add(path46);
2140
2273
  } else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
2141
2274
  ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
2142
2275
  }
@@ -2146,10 +2279,10 @@ function walk(ctx, raw) {
2146
2279
  layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
2147
2280
  const gapId = boundId(raw["itemSpacing"]);
2148
2281
  if (gapId !== void 0) {
2149
- const path45 = resolveBinding(ctx, gapId);
2150
- if (path45 !== void 0) {
2151
- layout.gap = path45;
2152
- tokens.add(path45);
2282
+ const path46 = resolveBinding(ctx, gapId);
2283
+ if (path46 !== void 0) {
2284
+ layout.gap = path46;
2285
+ tokens.add(path46);
2153
2286
  }
2154
2287
  } else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
2155
2288
  ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
@@ -2158,10 +2291,10 @@ function walk(ctx, raw) {
2158
2291
  for (const field of PADDING_FIELDS) {
2159
2292
  const id = boundId(raw[field]);
2160
2293
  if (id !== void 0) {
2161
- const path45 = resolveBinding(ctx, id);
2162
- if (path45 !== void 0) {
2163
- paddingPaths.push(path45);
2164
- tokens.add(path45);
2294
+ const path46 = resolveBinding(ctx, id);
2295
+ if (path46 !== void 0) {
2296
+ paddingPaths.push(path46);
2297
+ tokens.add(path46);
2165
2298
  }
2166
2299
  } else if (typeof raw[field] === "number" && raw[field] !== 0) {
2167
2300
  ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
@@ -2369,25 +2502,25 @@ var init_src3 = __esm({
2369
2502
  });
2370
2503
 
2371
2504
  // packages/cli/src/invocation.ts
2372
- import { existsSync as existsSync4, realpathSync } from "node:fs";
2373
- import path4 from "node:path";
2505
+ import { existsSync as existsSync5, realpathSync } from "node:fs";
2506
+ import path5 from "node:path";
2374
2507
  import { fileURLToPath } from "node:url";
2375
2508
  function findPathTendril(pathEnv, platform) {
2376
- const dirs = pathEnv.split(path4.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
2509
+ const dirs = pathEnv.split(path5.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
2377
2510
  const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
2378
2511
  for (const dir of dirs) {
2379
2512
  for (const name of names) {
2380
- const candidate = path4.join(dir, name);
2381
- if (existsSync4(candidate)) return candidate;
2513
+ const candidate = path5.join(dir, name);
2514
+ if (existsSync5(candidate)) return candidate;
2382
2515
  }
2383
2516
  }
2384
2517
  return null;
2385
2518
  }
2386
2519
  function packageRootOf(file) {
2387
- let dir = path4.dirname(file);
2520
+ let dir = path5.dirname(file);
2388
2521
  for (; ; ) {
2389
- if (existsSync4(path4.join(dir, "package.json"))) return dir;
2390
- const parent = path4.dirname(dir);
2522
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
2523
+ const parent = path5.dirname(dir);
2391
2524
  if (parent === dir) return null;
2392
2525
  dir = parent;
2393
2526
  }
@@ -2426,18 +2559,18 @@ var init_invocation = __esm({
2426
2559
 
2427
2560
  // packages/verify/src/browser.ts
2428
2561
  import { execFileSync } from "node:child_process";
2429
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
2430
- import path5 from "node:path";
2562
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
2563
+ import path6 from "node:path";
2431
2564
  function resolveChrome() {
2432
2565
  const fromEnv = process.env["TENDRIL_CHROME"] ?? process.env["CHROME_PATH"];
2433
2566
  if (fromEnv !== void 0 && fromEnv !== "") {
2434
- if (!existsSync5(fromEnv)) {
2567
+ if (!existsSync6(fromEnv)) {
2435
2568
  throw new Error(`CHROME_PATH points at ${fromEnv}, which does not exist \u2014 fix the variable or unset it to use discovery`);
2436
2569
  }
2437
2570
  return fromEnv;
2438
2571
  }
2439
2572
  const candidates = process.platform === "darwin" ? MAC_CANDIDATES : process.platform === "win32" ? WIN_CANDIDATES : [];
2440
- for (const c of candidates) if (existsSync5(c)) return c;
2573
+ for (const c of candidates) if (existsSync6(c)) return c;
2441
2574
  if (process.platform !== "win32") {
2442
2575
  for (const name of PATH_NAMES) {
2443
2576
  try {
@@ -2451,7 +2584,7 @@ function resolveChrome() {
2451
2584
  }
2452
2585
  function versionFromInstallDir(exePath) {
2453
2586
  try {
2454
- const builds = readdirSync2(path5.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
2587
+ const builds = readdirSync2(path6.dirname(exePath), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\d+(\.\d+){3}$/.test(e.name)).map((e) => e.name.split(".").map(Number));
2455
2588
  builds.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2] || a[3] - b[3]);
2456
2589
  return builds.length === 0 ? null : builds[builds.length - 1].join(".");
2457
2590
  } catch {
@@ -2463,7 +2596,7 @@ function windowsBrowserName(exePath) {
2463
2596
  if (p.includes("\\google\\chrome\\")) return "Google Chrome";
2464
2597
  if (p.includes("\\microsoft\\edge\\")) return "Microsoft Edge";
2465
2598
  if (p.includes("chromium")) return "Chromium";
2466
- return path5.win32.basename(exePath, ".exe");
2599
+ return path6.win32.basename(exePath, ".exe");
2467
2600
  }
2468
2601
  function chromeVersion() {
2469
2602
  if (_version !== void 0) return _version;
@@ -2498,32 +2631,32 @@ var init_browser = __esm({
2498
2631
  });
2499
2632
 
2500
2633
  // packages/verify/src/runtime.ts
2501
- import { existsSync as existsSync6, mkdtempSync, symlinkSync } from "node:fs";
2634
+ import { existsSync as existsSync7, mkdtempSync, symlinkSync } from "node:fs";
2502
2635
  import { createRequire } from "node:module";
2503
2636
  import os from "node:os";
2504
- import path6 from "node:path";
2637
+ import path7 from "node:path";
2505
2638
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2506
2639
  function runtimePackageRoot() {
2507
2640
  const env = process.env["TENDRIL_PACKAGE_ROOT"];
2508
- if (env !== void 0 && env !== "") return path6.resolve(env);
2509
- return path6.resolve(path6.dirname(fileURLToPath2(import.meta.url)), "..");
2641
+ if (env !== void 0 && env !== "") return path7.resolve(env);
2642
+ return path7.resolve(path7.dirname(fileURLToPath2(import.meta.url)), "..");
2510
2643
  }
2511
2644
  function runtimeNodeModules() {
2512
2645
  const root = runtimePackageRoot();
2513
- for (let dir = root; ; dir = path6.dirname(dir)) {
2514
- const candidate = path6.basename(dir) === "node_modules" ? dir : path6.join(dir, "node_modules");
2515
- if (existsSync6(path6.join(candidate, "react"))) return candidate;
2516
- if (path6.dirname(dir) === dir) break;
2646
+ for (let dir = root; ; dir = path7.dirname(dir)) {
2647
+ const candidate = path7.basename(dir) === "node_modules" ? dir : path7.join(dir, "node_modules");
2648
+ if (existsSync7(path7.join(candidate, "react"))) return candidate;
2649
+ if (path7.dirname(dir) === dir) break;
2517
2650
  }
2518
2651
  throw new Error(
2519
2652
  `Tendril's installed dependencies are missing: no node_modules containing react found at or above ${root}. This is an INSTALLATION problem, not a component error \u2014 reinstall with \`npm install -g @tendrilapp/cli\` (or \`tendrilapp\`) and retry.`
2520
2653
  );
2521
2654
  }
2522
2655
  function newScratchDir(prefix) {
2523
- const dir = mkdtempSync(path6.join(os.tmpdir(), `tendril-${prefix}-`));
2656
+ const dir = mkdtempSync(path7.join(os.tmpdir(), `tendril-${prefix}-`));
2524
2657
  const nodeModules = runtimeNodeModules();
2525
2658
  try {
2526
- symlinkSync(nodeModules, path6.join(dir, "node_modules"), "junction");
2659
+ symlinkSync(nodeModules, path7.join(dir, "node_modules"), "junction");
2527
2660
  } catch (err) {
2528
2661
  throw new Error(
2529
2662
  `Tendril could not link its dependencies into the scratch dir (${nodeModules} -> ${dir}): ${err instanceof Error ? err.message : String(err)}. This is an environment problem, not a component error.`
@@ -2532,7 +2665,7 @@ function newScratchDir(prefix) {
2532
2665
  return dir;
2533
2666
  }
2534
2667
  function reactPinPlugin() {
2535
- const req = createRequire(path6.join(runtimePackageRoot(), "package.json"));
2668
+ const req = createRequire(path7.join(runtimePackageRoot(), "package.json"));
2536
2669
  return {
2537
2670
  name: "tendril-react-pin",
2538
2671
  setup(b) {
@@ -2585,9 +2718,9 @@ var init_gates = __esm({
2585
2718
 
2586
2719
  // packages/verify/src/tsc-check.ts
2587
2720
  import ts from "typescript";
2588
- import path7 from "node:path";
2721
+ import path8 from "node:path";
2589
2722
  function runTscStrict(files) {
2590
- const program = ts.createProgram(files.map((f) => path7.resolve(f)), STRICT_OPTIONS);
2723
+ const program = ts.createProgram(files.map((f) => path8.resolve(f)), STRICT_OPTIONS);
2591
2724
  const diagnostics = ts.getPreEmitDiagnostics(program);
2592
2725
  const mapped = diagnostics.map((d) => {
2593
2726
  const file = d.file?.fileName;
@@ -2748,7 +2881,7 @@ var init_token_lint = __esm({
2748
2881
 
2749
2882
  // packages/verify/src/loop.ts
2750
2883
  import { rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2751
- import path8 from "node:path";
2884
+ import path9 from "node:path";
2752
2885
  async function runChecks(componentName, files, extraFiles, definedVars2) {
2753
2886
  const findings = [];
2754
2887
  for (const violation of scanForbiddenPatterns(files.tsx)) {
@@ -2763,10 +2896,10 @@ async function runChecks(componentName, files, extraFiles, definedVars2) {
2763
2896
  }
2764
2897
  const workDir = newScratchDir("loop");
2765
2898
  try {
2766
- const tsxPath = path8.join(workDir, `${componentName}.tsx`);
2899
+ const tsxPath = path9.join(workDir, `${componentName}.tsx`);
2767
2900
  writeFileSync2(tsxPath, files.tsx);
2768
2901
  for (const [name, content] of Object.entries(extraFiles ?? {})) {
2769
- writeFileSync2(path8.join(workDir, name), content);
2902
+ writeFileSync2(path9.join(workDir, name), content);
2770
2903
  }
2771
2904
  const tsc = runTscStrict([tsxPath]);
2772
2905
  for (const d of tsc.diagnostics) {
@@ -3417,7 +3550,7 @@ var init_image_diff = __esm({
3417
3550
  });
3418
3551
 
3419
3552
  // packages/verify/src/visual-facts.ts
3420
- import path9 from "node:path";
3553
+ import path10 from "node:path";
3421
3554
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3422
3555
  import { build } from "esbuild";
3423
3556
  import { chromium } from "playwright-core";
@@ -3962,7 +4095,7 @@ var init_visual_facts = __esm({
3962
4095
  init_browser();
3963
4096
  init_mount_limits();
3964
4097
  init_image_diff();
3965
- RESOLVE_DIR = path9.resolve(path9.dirname(fileURLToPath3(import.meta.url)), "..");
4098
+ RESOLVE_DIR = path10.resolve(path10.dirname(fileURLToPath3(import.meta.url)), "..");
3966
4099
  TOLERANCE_PX = 2;
3967
4100
  WIDTH_SLACK = 0.25;
3968
4101
  IMAGE_SIMILARITY_FLOOR = 0.8;
@@ -3971,14 +4104,14 @@ var init_visual_facts = __esm({
3971
4104
  });
3972
4105
 
3973
4106
  // packages/verify/src/paths.ts
3974
- import path10 from "node:path";
4107
+ import path11 from "node:path";
3975
4108
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3976
4109
  var VERIFY_PKG_DIR, REPO_ROOT;
3977
4110
  var init_paths = __esm({
3978
4111
  "packages/verify/src/paths.ts"() {
3979
4112
  "use strict";
3980
- VERIFY_PKG_DIR = path10.resolve(path10.dirname(fileURLToPath4(import.meta.url)), "..");
3981
- REPO_ROOT = path10.resolve(VERIFY_PKG_DIR, "..", "..");
4113
+ VERIFY_PKG_DIR = path11.resolve(path11.dirname(fileURLToPath4(import.meta.url)), "..");
4114
+ REPO_ROOT = path11.resolve(VERIFY_PKG_DIR, "..", "..");
3982
4115
  }
3983
4116
  });
3984
4117
 
@@ -4097,9 +4230,9 @@ var init_font_collection = __esm({
4097
4230
  });
4098
4231
 
4099
4232
  // packages/verify/src/font-discovery.ts
4100
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "node:fs";
4233
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2 } from "node:fs";
4101
4234
  import os2 from "node:os";
4102
- import path11 from "node:path";
4235
+ import path12 from "node:path";
4103
4236
  function weightFromSubfamily(subfamily) {
4104
4237
  for (const [re, w] of WEIGHT_TOKENS) if (re.test(subfamily)) return w;
4105
4238
  return void 0;
@@ -4145,7 +4278,7 @@ function faceAt(bytes, view, dirOffset, file, faceIndex) {
4145
4278
  }
4146
4279
  function facesInFile(file) {
4147
4280
  try {
4148
- const bytes = new Uint8Array(readFileSync4(file));
4281
+ const bytes = new Uint8Array(readFileSync5(file));
4149
4282
  if (bytes.length < 12) return [];
4150
4283
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
4151
4284
  if (isCollection(bytes)) {
@@ -4165,13 +4298,13 @@ function facesInFile(file) {
4165
4298
  }
4166
4299
  function systemFontDirs() {
4167
4300
  const env = process.env["TENDRIL_SYSTEM_FONT_DIRS"];
4168
- if (env !== void 0 && env !== "") return env.split(path11.delimiter).filter((d) => d !== "" && existsSync7(d));
4301
+ if (env !== void 0 && env !== "") return env.split(path12.delimiter).filter((d) => d !== "" && existsSync8(d));
4169
4302
  const home = os2.homedir();
4170
- const dirs = process.platform === "darwin" ? ["/System/Library/Fonts", "/Library/Fonts", path11.join(home, "Library", "Fonts")] : process.platform === "win32" ? [
4171
- path11.join(process.env["WINDIR"] ?? "C:\\Windows", "Fonts"),
4172
- ...process.env["LOCALAPPDATA"] !== void 0 ? [path11.join(process.env["LOCALAPPDATA"], "Microsoft", "Windows", "Fonts")] : []
4173
- ] : ["/usr/share/fonts", "/usr/local/share/fonts", path11.join(home, ".fonts"), path11.join(home, ".local", "share", "fonts")];
4174
- return dirs.filter((d) => existsSync7(d));
4303
+ const dirs = process.platform === "darwin" ? ["/System/Library/Fonts", "/Library/Fonts", path12.join(home, "Library", "Fonts")] : process.platform === "win32" ? [
4304
+ path12.join(process.env["WINDIR"] ?? "C:\\Windows", "Fonts"),
4305
+ ...process.env["LOCALAPPDATA"] !== void 0 ? [path12.join(process.env["LOCALAPPDATA"], "Microsoft", "Windows", "Fonts")] : []
4306
+ ] : ["/usr/share/fonts", "/usr/local/share/fonts", path12.join(home, ".fonts"), path12.join(home, ".local", "share", "fonts")];
4307
+ return dirs.filter((d) => existsSync8(d));
4175
4308
  }
4176
4309
  function discoverSystemFaces(dirs = systemFontDirs(), depth = 3) {
4177
4310
  const seen = /* @__PURE__ */ new Set();
@@ -4185,10 +4318,10 @@ function discoverSystemFaces(dirs = systemFontDirs(), depth = 3) {
4185
4318
  continue;
4186
4319
  }
4187
4320
  for (const e of entries) {
4188
- const full = path11.join(dir, e.name);
4321
+ const full = path12.join(dir, e.name);
4189
4322
  if (e.isDirectory()) {
4190
4323
  if (remaining > 1) faces.push(...walk2([full], remaining - 1));
4191
- } else if (FONT_EXTENSIONS.has(path11.extname(e.name).toLowerCase())) {
4324
+ } else if (FONT_EXTENSIONS.has(path12.extname(e.name).toLowerCase())) {
4192
4325
  let key = full;
4193
4326
  try {
4194
4327
  key = realpathSync2(full);
@@ -4237,13 +4370,13 @@ var init_font_discovery = __esm({
4237
4370
 
4238
4371
  // packages/verify/src/font-resolve.ts
4239
4372
  import { createHash as createHash2 } from "node:crypto";
4240
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
4373
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
4241
4374
  import os3 from "node:os";
4242
- import path12 from "node:path";
4375
+ import path13 from "node:path";
4243
4376
  function fontCacheDir() {
4244
4377
  const env = process.env["TENDRIL_FONT_CACHE"];
4245
- if (env !== void 0 && env !== "") return path12.resolve(env);
4246
- return path12.join(os3.homedir(), ".tendril", "fonts");
4378
+ if (env !== void 0 && env !== "") return path13.resolve(env);
4379
+ return path13.join(os3.homedir(), ".tendril", "fonts");
4247
4380
  }
4248
4381
  function normalizeFontLicense(value) {
4249
4382
  return typeof value === "string" && FONT_LICENSES.includes(value) ? value : "unknown";
@@ -4308,29 +4441,29 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
4308
4441
  }
4309
4442
  const bytes = new Uint8Array(await fileRes.arrayBuffer());
4310
4443
  const sha256 = createHash2("sha256").update(bytes).digest("hex");
4311
- const file = path12.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
4444
+ const file = path13.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}.woff2`);
4312
4445
  writeFileSync3(file, bytes);
4313
4446
  resolved.push({ family, weight, source: url, sha256, file, license: meta.license, ...meta.family !== null ? { servedFamily: meta.family } : {} });
4314
4447
  } catch (err) {
4315
4448
  failures.push({ family, weight, reason: `download failed: ${err instanceof Error ? err.message : String(err)}` });
4316
4449
  }
4317
4450
  }
4318
- const mPath = path12.join(cacheDir, "manifest.json");
4319
- const prior = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4320
- const portable2 = resolved.map((m) => ({ ...m, file: path12.basename(m.file) }));
4451
+ const mPath = path13.join(cacheDir, "manifest.json");
4452
+ const prior = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4453
+ const portable2 = resolved.map((m) => ({ ...m, file: path13.basename(m.file) }));
4321
4454
  const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
4322
4455
  if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
4323
4456
  `);
4324
4457
  return { resolved, failures };
4325
4458
  }
4326
4459
  function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, faceIndex, provenance = "local") {
4327
- const src = path12.resolve(filePath);
4328
- if (!existsSync8(src)) throw new Error(`font file not found: ${src}`);
4329
- const ext = path12.extname(src).toLowerCase();
4460
+ const src = path13.resolve(filePath);
4461
+ if (!existsSync9(src)) throw new Error(`font file not found: ${src}`);
4462
+ const ext = path13.extname(src).toLowerCase();
4330
4463
  if (![".woff2", ".woff", ".ttf", ".otf", ".ttc"].includes(ext)) {
4331
4464
  throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf, .otf or .ttc`);
4332
4465
  }
4333
- let bytes = new Uint8Array(readFileSync5(src));
4466
+ let bytes = new Uint8Array(readFileSync6(src));
4334
4467
  if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
4335
4468
  let storedExt = ext;
4336
4469
  if (isCollection(bytes)) {
@@ -4340,7 +4473,7 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, f
4340
4473
  const all = listCollectionFaces(bytes);
4341
4474
  const shown = (candidates.length > 0 ? candidates : all).map((f) => ` --face ${f.index} ${f.family ?? "(unnamed)"}${f.subfamily !== void 0 ? ` ${f.subfamily}` : ""}`).join("\n");
4342
4475
  throw new Error(
4343
- `${path12.basename(src)} is a collection of ${all.length} faces and ${candidates.length === 0 ? `none is named "${family}"` : `${candidates.length} match "${family}"`} \u2014 name the one you mean with --face <index>:
4476
+ `${path13.basename(src)} is a collection of ${all.length} faces and ${candidates.length === 0 ? `none is named "${family}"` : `${candidates.length} match "${family}"`} \u2014 name the one you mean with --face <index>:
4344
4477
  ${shown}`
4345
4478
  );
4346
4479
  }
@@ -4349,20 +4482,20 @@ ${shown}`
4349
4482
  }
4350
4483
  mkdirSync2(cacheDir, { recursive: true });
4351
4484
  const sha256 = createHash2("sha256").update(bytes).digest("hex");
4352
- const file = path12.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${storedExt}`);
4485
+ const file = path13.join(cacheDir, `${family.toLowerCase().replace(/\s+/g, "-")}-${weight}${storedExt}`);
4353
4486
  writeFileSync3(file, bytes);
4354
- const face = { family, weight, source: `${provenance}:${path12.basename(src)}`, sha256, file, license: "unknown" };
4355
- const mPath = path12.join(cacheDir, "manifest.json");
4356
- const prior = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4357
- const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path12.basename(file) }];
4487
+ const face = { family, weight, source: `${provenance}:${path13.basename(src)}`, sha256, file, license: "unknown" };
4488
+ const mPath = path13.join(cacheDir, "manifest.json");
4489
+ const prior = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4490
+ const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path13.basename(file) }];
4358
4491
  writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
4359
4492
  `);
4360
4493
  return face;
4361
4494
  }
4362
4495
  function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
4363
- const lock = JSON.parse(readFileSync5(lockPath, "utf8"));
4364
- const mPath = path12.join(cacheDir, "manifest.json");
4365
- const manifest = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4496
+ const lock = JSON.parse(readFileSync6(lockPath, "utf8"));
4497
+ const mPath = path13.join(cacheDir, "manifest.json");
4498
+ const manifest = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4366
4499
  return lock.map((l) => {
4367
4500
  const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
4368
4501
  if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
@@ -4370,11 +4503,11 @@ function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
4370
4503
  });
4371
4504
  }
4372
4505
  function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4373
- const mPath = path12.join(cacheDir, "manifest.json");
4374
- if (!existsSync8(mPath)) return [];
4506
+ const mPath = path13.join(cacheDir, "manifest.json");
4507
+ if (!existsSync9(mPath)) return [];
4375
4508
  let entries;
4376
4509
  try {
4377
- entries = JSON.parse(readFileSync5(mPath, "utf8"));
4510
+ entries = JSON.parse(readFileSync6(mPath, "utf8"));
4378
4511
  } catch {
4379
4512
  return [];
4380
4513
  }
@@ -4388,8 +4521,8 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4388
4521
  return [...byFamily.entries()].map(([family, w]) => ({ family, weights: [...w].sort((a, b) => a - b) }));
4389
4522
  }
4390
4523
  function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
4391
- const mPath = path12.join(cacheDir, "manifest.json");
4392
- const manifest = existsSync8(mPath) ? JSON.parse(readFileSync5(mPath, "utf8")) : [];
4524
+ const mPath = path13.join(cacheDir, "manifest.json");
4525
+ const manifest = existsSync9(mPath) ? JSON.parse(readFileSync6(mPath, "utf8")) : [];
4393
4526
  const wanted = new Set(families.map((f) => f.toLowerCase()));
4394
4527
  return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
4395
4528
  family: f.family,
@@ -4402,11 +4535,11 @@ function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
4402
4535
  }));
4403
4536
  }
4404
4537
  function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
4405
- const mPath = path12.join(cacheDir, "manifest.json");
4406
- if (!existsSync8(mPath)) return [];
4538
+ const mPath = path13.join(cacheDir, "manifest.json");
4539
+ if (!existsSync9(mPath)) return [];
4407
4540
  let entries;
4408
4541
  try {
4409
- entries = JSON.parse(readFileSync5(mPath, "utf8"));
4542
+ entries = JSON.parse(readFileSync6(mPath, "utf8"));
4410
4543
  } catch {
4411
4544
  return [];
4412
4545
  }
@@ -4415,20 +4548,20 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
4415
4548
  ).map((e) => ({ family: e.family, weight: e.weight, sha256: e.sha256 }));
4416
4549
  }
4417
4550
  function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
4418
- const mPath = path12.join(cacheDir, "manifest.json");
4419
- if (!existsSync8(mPath)) return [];
4551
+ const mPath = path13.join(cacheDir, "manifest.json");
4552
+ if (!existsSync9(mPath)) return [];
4420
4553
  let entries;
4421
4554
  try {
4422
- entries = JSON.parse(readFileSync5(mPath, "utf8"));
4555
+ entries = JSON.parse(readFileSync6(mPath, "utf8"));
4423
4556
  } catch {
4424
4557
  return [];
4425
4558
  }
4426
4559
  const byFamily = /* @__PURE__ */ new Map();
4427
4560
  for (const e of entries) {
4428
4561
  if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
4429
- const file = path12.isAbsolute(e.file) && existsSync8(e.file) ? e.file : path12.resolve(cacheDir, path12.basename(e.file));
4430
- if (!existsSync8(file)) continue;
4431
- if (createHash2("sha256").update(readFileSync5(file)).digest("hex") !== e.sha256) continue;
4562
+ const file = path13.isAbsolute(e.file) && existsSync9(e.file) ? e.file : path13.resolve(cacheDir, path13.basename(e.file));
4563
+ if (!existsSync9(file)) continue;
4564
+ if (createHash2("sha256").update(readFileSync6(file)).digest("hex") !== e.sha256) continue;
4432
4565
  const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
4433
4566
  set.add(e.weight);
4434
4567
  byFamily.set(e.family, set);
@@ -4449,8 +4582,8 @@ function addSystemFamily(family, opts = {}) {
4449
4582
  const skipped = [];
4450
4583
  const overwrote = [];
4451
4584
  const cacheDir = opts.cacheDir ?? DEFAULT_FONT_CACHE;
4452
- const manifestFile = path12.join(cacheDir, "manifest.json");
4453
- const prior = existsSync8(manifestFile) ? JSON.parse(readFileSync5(manifestFile, "utf8")) : [];
4585
+ const manifestFile = path13.join(cacheDir, "manifest.json");
4586
+ const prior = existsSync9(manifestFile) ? JSON.parse(readFileSync6(manifestFile, "utf8")) : [];
4454
4587
  const taken = /* @__PURE__ */ new Set();
4455
4588
  for (const face of faces) {
4456
4589
  const skip = (reason) => skipped.push({ subfamily: face.subfamily, weight: face.weight, reason });
@@ -4512,17 +4645,17 @@ var init_font_resolve = __esm({
4512
4645
 
4513
4646
  // packages/verify/src/font-faces.ts
4514
4647
  import { createHash as createHash3 } from "node:crypto";
4515
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
4516
- import path13 from "node:path";
4648
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
4649
+ import path14 from "node:path";
4517
4650
  function injectedGroups(manifestPath2) {
4518
- if (!existsSync9(manifestPath2)) return { groups: [], shared: false };
4519
- const claimed = JSON.parse(readFileSync6(manifestPath2, "utf8"));
4520
- const resolveFile = (f) => path13.isAbsolute(f) && existsSync9(f) ? f : path13.resolve(path13.dirname(manifestPath2), path13.basename(f));
4651
+ if (!existsSync10(manifestPath2)) return { groups: [], shared: false };
4652
+ const claimed = JSON.parse(readFileSync7(manifestPath2, "utf8"));
4653
+ const resolveFile = (f) => path14.isAbsolute(f) && existsSync10(f) ? f : path14.resolve(path14.dirname(manifestPath2), path14.basename(f));
4521
4654
  const byFile = /* @__PURE__ */ new Map();
4522
4655
  for (const f of claimed) {
4523
4656
  const file = resolveFile(f.file);
4524
- if (!existsSync9(file)) continue;
4525
- if (createHash3("sha256").update(readFileSync6(file)).digest("hex") !== f.sha256) continue;
4657
+ if (!existsSync10(file)) continue;
4658
+ if (createHash3("sha256").update(readFileSync7(file)).digest("hex") !== f.sha256) continue;
4526
4659
  const k = `${f.family}:${f.file}`;
4527
4660
  const e = byFile.get(k) ?? { family: f.family, weights: [], file };
4528
4661
  e.weights.push(f.weight);
@@ -4531,14 +4664,14 @@ function injectedGroups(manifestPath2) {
4531
4664
  const groups = [...byFile.values()];
4532
4665
  return { groups, shared: new Set(groups.map((e) => e.file)).size < groups.length };
4533
4666
  }
4534
- function fontFaceCss(manifestPath2 = path13.join(fontCacheDir(), "manifest.json")) {
4667
+ function fontFaceCss(manifestPath2 = path14.join(fontCacheDir(), "manifest.json")) {
4535
4668
  const { groups, shared } = injectedGroups(manifestPath2);
4536
4669
  return groups.map((e) => {
4537
4670
  const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
4538
- return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync6(e.file).toString("base64")}) format('woff2'); }`;
4671
+ return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync7(e.file).toString("base64")}) format('woff2'); }`;
4539
4672
  }).join("\n");
4540
4673
  }
4541
- function injectedFamilyWeights(manifestPath2 = path13.join(fontCacheDir(), "manifest.json")) {
4674
+ function injectedFamilyWeights(manifestPath2 = path14.join(fontCacheDir(), "manifest.json")) {
4542
4675
  const { groups, shared } = injectedGroups(manifestPath2);
4543
4676
  const out = /* @__PURE__ */ new Map();
4544
4677
  for (const g of groups) {
@@ -4563,17 +4696,17 @@ var init_font_faces = __esm({
4563
4696
  });
4564
4697
 
4565
4698
  // packages/verify/src/admission.ts
4566
- import { readFileSync as readFileSync7, readdirSync as readdirSync4, existsSync as existsSync10, writeFileSync as writeFileSync4 } from "node:fs";
4567
- import path14 from "node:path";
4699
+ import { readFileSync as readFileSync8, readdirSync as readdirSync4, existsSync as existsSync11, writeFileSync as writeFileSync4 } from "node:fs";
4700
+ import path15 from "node:path";
4568
4701
  import { build as build2 } from "esbuild";
4569
4702
  import postcss from "postcss";
4570
4703
  import tailwindcss from "tailwindcss";
4571
4704
  import { chromium as chromium2 } from "playwright-core";
4572
4705
  function fontWeightsByFamily() {
4573
- const mPath = path14.join(fontCacheDir(), "manifest.json");
4706
+ const mPath = path15.join(fontCacheDir(), "manifest.json");
4574
4707
  const out = /* @__PURE__ */ new Map();
4575
- if (!existsSync10(mPath)) return out;
4576
- for (const f of JSON.parse(readFileSync7(mPath, "utf8")))
4708
+ if (!existsSync11(mPath)) return out;
4709
+ for (const f of JSON.parse(readFileSync8(mPath, "utf8")))
4577
4710
  out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
4578
4711
  return out;
4579
4712
  }
@@ -4593,26 +4726,26 @@ var init_admission = __esm({
4593
4726
  });
4594
4727
 
4595
4728
  // packages/verify/src/candidate-css.ts
4596
- import { existsSync as existsSync11, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4597
- import path15 from "node:path";
4729
+ import { existsSync as existsSync12, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4730
+ import path16 from "node:path";
4598
4731
  function candidateCss(bundleDir) {
4599
- const files = ["tokens.css", "styles.css"].map((f) => path15.join(bundleDir, f));
4600
- const composedRoot = path15.join(bundleDir, "composed");
4732
+ const files = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f));
4733
+ const composedRoot = path16.join(bundleDir, "composed");
4601
4734
  let composedDirs = [];
4602
4735
  try {
4603
4736
  composedDirs = readdirSync5(composedRoot).sort();
4604
4737
  } catch {
4605
4738
  }
4606
4739
  for (const entry of composedDirs) {
4607
- const dir = path15.join(composedRoot, entry);
4740
+ const dir = path16.join(composedRoot, entry);
4608
4741
  try {
4609
4742
  if (!statSync2(dir).isDirectory()) continue;
4610
4743
  } catch {
4611
4744
  continue;
4612
4745
  }
4613
- files.push(path15.join(dir, "tokens.css"), path15.join(dir, "styles.css"));
4746
+ files.push(path16.join(dir, "tokens.css"), path16.join(dir, "styles.css"));
4614
4747
  }
4615
- return files.filter((f) => existsSync11(f)).map((f) => readFileSync8(f, "utf8")).join("\n");
4748
+ return files.filter((f) => existsSync12(f)).map((f) => readFileSync9(f, "utf8")).join("\n");
4616
4749
  }
4617
4750
  var init_candidate_css = __esm({
4618
4751
  "packages/verify/src/candidate-css.ts"() {
@@ -4621,12 +4754,18 @@ var init_candidate_css = __esm({
4621
4754
  });
4622
4755
 
4623
4756
  // packages/verify/src/tasks.ts
4624
- import path16 from "node:path";
4625
- var CALENDAR_CONFIGS, CALENDAR_API, CALENDAR_BEHAVIORS, BUTTON_CONFIGS, BUTTON_API, COMBO_FIX, COMBO_CONFIGS, COMBO_API, MODAL_CONFIGS, MODAL_API, BUTTON_BEHAVIORS, COMBO_BEHAVIORS, MODAL_BEHAVIORS, TASKS;
4757
+ import path17 from "node:path";
4758
+ var CONTRACT_CHECK_IDS, CALENDAR_CONFIGS, CALENDAR_API, CALENDAR_BEHAVIORS, BUTTON_CONFIGS, BUTTON_API, COMBO_FIX, COMBO_CONFIGS, COMBO_API, MODAL_CONFIGS, MODAL_API, BUTTON_BEHAVIORS, COMBO_BEHAVIORS, MODAL_BEHAVIORS, TASKS;
4626
4759
  var init_tasks = __esm({
4627
4760
  "packages/verify/src/tasks.ts"() {
4628
4761
  "use strict";
4629
4762
  init_paths();
4763
+ CONTRACT_CHECK_IDS = /* @__PURE__ */ new Set([
4764
+ "ref-reaches-control",
4765
+ "rest-props-reach-control",
4766
+ "default-value-reaches-control",
4767
+ "error-pose-wires-aria"
4768
+ ]);
4630
4769
  CALENDAR_CONFIGS = [
4631
4770
  { rep: "calendar-default", component: "Calendar", props: { style: "Default", selectedDay: 7, today: 28 } },
4632
4771
  { rep: "calendar-new-york", component: "Calendar", props: { style: "New York", selectedDay: 7, today: 28 } },
@@ -4781,7 +4920,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4781
4920
  ];
4782
4921
  TASKS = {
4783
4922
  calendar: {
4784
- set: path16.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
4923
+ set: path17.join(REPO_ROOT, "examples/recordings/shadcn-poc-calendar"),
4785
4924
  entry: "Calendar.tsx",
4786
4925
  configs: CALENDAR_CONFIGS,
4787
4926
  systemApi: CALENDAR_API,
@@ -4789,7 +4928,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4789
4928
  prelude: { controls: ['[data-tendril-part="day"]'], textInputs: [] }
4790
4929
  },
4791
4930
  "shadcn-button": {
4792
- set: path16.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
4931
+ set: path17.join(REPO_ROOT, "examples/recordings/shadcn-poc-button"),
4793
4932
  entry: "Button.tsx",
4794
4933
  configs: BUTTON_CONFIGS,
4795
4934
  systemApi: BUTTON_API,
@@ -4797,7 +4936,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4797
4936
  prelude: { controls: ["> *"], textInputs: [] }
4798
4937
  },
4799
4938
  combobox: {
4800
- set: path16.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
4939
+ set: path17.join(REPO_ROOT, "examples/recordings/carbon-poc-combobox"),
4801
4940
  entry: "ComboBox.tsx",
4802
4941
  configs: COMBO_CONFIGS,
4803
4942
  systemApi: COMBO_API,
@@ -4805,7 +4944,7 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4805
4944
  prelude: { controls: ['[role="option"]'], textInputs: ["input"], popover: { selector: '[role="listbox"]', trigger: "input" } }
4806
4945
  },
4807
4946
  modal: {
4808
- set: path16.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
4947
+ set: path17.join(REPO_ROOT, "examples/recordings/carbon-poc-modal"),
4809
4948
  entry: "Modal.tsx",
4810
4949
  configs: MODAL_CONFIGS,
4811
4950
  systemApi: MODAL_API,
@@ -4819,12 +4958,13 @@ BEHAVIORAL CONTRACT (machine-verified, gating): the primary action and close con
4819
4958
  // packages/verify/src/behavior.ts
4820
4959
  var behavior_exports = {};
4821
4960
  __export(behavior_exports, {
4961
+ POSE_GAP_FLOOR: () => POSE_GAP_FLOOR,
4822
4962
  checkBehaviors: () => checkBehaviors,
4823
4963
  compileMount: () => compileMount,
4824
4964
  recordingIsDark: () => recordingIsDark
4825
4965
  });
4826
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
4827
- import path17 from "node:path";
4966
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
4967
+ import path18 from "node:path";
4828
4968
  import { build as build3 } from "esbuild";
4829
4969
  import { chromium as chromium3 } from "playwright-core";
4830
4970
  import { PNG as PNG2 } from "pngjs";
@@ -4833,13 +4973,13 @@ function getFontFaces() {
4833
4973
  return _fontFaces;
4834
4974
  }
4835
4975
  async function compileMount(task, bundleDir) {
4836
- const entryTsx = path17.join(bundleDir, task.entry);
4837
- if (!existsSync12(entryTsx)) return { error: `${task.entry} missing` };
4976
+ const entryTsx = path18.join(bundleDir, task.entry);
4977
+ if (!existsSync13(entryTsx)) return { error: `${task.entry} missing` };
4838
4978
  const mountSrc = `
4839
4979
  import { createElement } from "react";
4840
4980
  import { createRoot } from "react-dom/client";
4841
- import * as B from ${JSON.stringify(path17.resolve(entryTsx))};
4842
- const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; spyProps?: string[] } }).__cfg;
4981
+ import * as B from ${JSON.stringify(path18.resolve(entryTsx))};
4982
+ const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; spyProps?: string[]; refProbe?: boolean } }).__cfg;
4843
4983
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
4844
4984
  // Callbacks cannot ride the JSON config: specs NAME spy props and the
4845
4985
  // mount builds the functions, recording firings for dismissNotifies.
@@ -4850,6 +4990,15 @@ for (const name of cfg.spyProps ?? []) {
4850
4990
  (w.__tendrilFired ??= {})[name] = true;
4851
4991
  };
4852
4992
  }
4993
+ // Ref probe (Cycle D contract): attached ONLY when the spec carries
4994
+ // assertRefReachesControl \u2014 attaching a ref to a plain function
4995
+ // component logs a React warning, and legacy bundles must stay clean
4996
+ // on the checks that do not ask.
4997
+ if (cfg.refProbe === true) {
4998
+ const ref = { current: null as unknown };
4999
+ (window as unknown as { __tendrilRef?: { current: unknown } }).__tendrilRef = ref;
5000
+ (props as Record<string, unknown>)["ref"] = ref;
5001
+ }
4853
5002
  const root = document.getElementById("root");
4854
5003
  if (root && C) createRoot(root).render(createElement(C, props));
4855
5004
  `;
@@ -4894,7 +5043,7 @@ async function runSteps(page, spec, renderPose) {
4894
5043
  await page.waitForTimeout(delay);
4895
5044
  await freezeAnimations(page);
4896
5045
  if (!(await shotRoot(page)).equals(idle)) {
4897
- return { id: spec.id, pass: false, detail: "unmeasurable: the component's pixels change at rest (free-running animation or timer) \u2014 the committed state cannot be observed through it" };
5046
+ return { id: spec.id, pass: false, detail: "unmeasurable: the component's pixels change at rest (free-running animation or a timer in YOUR candidate) \u2014 the committed state cannot be observed through it; this IS fixable from code: keep every scored pose reachable with all motion idle (freezable CSS animation, no JS-driven repaints)" };
4898
5047
  }
4899
5048
  }
4900
5049
  try {
@@ -4914,7 +5063,11 @@ async function runSteps(page, spec, renderPose) {
4914
5063
  const gap = 1 - fromIdle.similarity;
4915
5064
  const residual = 1 - toTarget.similarity;
4916
5065
  if (gap < POSE_GAP_FLOOR) {
4917
- return { id: spec.id, pass: false, detail: `unmeasurable: the "${pose}" pose renders indistinguishably from the resting pose (gap ${gap.toFixed(4)}), so committing cannot be observed` };
5066
+ return {
5067
+ id: spec.id,
5068
+ pass: false,
5069
+ detail: `unmeasurable: this bundle renders the "${pose}" pose indistinguishably from its resting pose (gap ${gap.toFixed(4)}) \u2014 if the recording's own references DO differ (authored tasks verify this before prescribing the check), the candidate is not painting the committed treatment: fix that pose's paint; if they do not, no code change can help \u2014 report it instead of iterating`
5070
+ };
4918
5071
  }
4919
5072
  if (residual > (1 - COMMIT_GAP_CLOSED) * gap) {
4920
5073
  return {
@@ -4934,7 +5087,7 @@ async function runSteps(page, spec, renderPose) {
4934
5087
  await page.waitForTimeout(150);
4935
5088
  } else if ("assertVisible" in step) {
4936
5089
  const visible = await page.evaluate(
4937
- `(() => { const el = document.querySelector('#root ${step.assertVisible}'); if (!el) return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0 && getComputedStyle(el).visibility !== 'hidden' && getComputedStyle(el).display !== 'none'; })()`
5090
+ `(() => { const el = document.querySelector('#root ${step.assertVisible}'); if (!el) return false; const r = el.getBoundingClientRect(); const cs = getComputedStyle(el); return r.width > 0 && r.height > 0 && cs.visibility !== 'hidden' && cs.display !== 'none' && parseFloat(cs.opacity) > 0; })()`
4938
5091
  );
4939
5092
  if (visible !== true) return { id: spec.id, pass: false, detail: `expected visible: ${step.assertVisible}` };
4940
5093
  } else if ("assertHidden" in step) {
@@ -4948,17 +5101,68 @@ async function runSteps(page, spec, renderPose) {
4948
5101
  if (cursor !== want) return { id: spec.id, pass: false, detail: `cursor on ${sel}: got "${String(cursor)}", want "${want}"` };
4949
5102
  } else if ("assertFocusable" in step) {
4950
5103
  const ok = await page.evaluate(
4951
- `(() => { const el = document.querySelector('#root ${step.assertFocusable}'); if (!el) return 'MISSING'; el.focus(); return document.activeElement === el ? true : 'not focusable (tag ' + el.tagName + ')'; })()`
5104
+ `(() => { const el = document.querySelector('#root ${step.assertFocusable}'); if (!el) return 'MISSING \u2014 no element matches: a real native control must exist here'; el.focus(); return document.activeElement === el ? true : 'not focusable (tag ' + el.tagName + ') \u2014 .focus() did not land: a display:none/visibility:hidden element cannot take focus, and a non-form element needs to be a native control (or the control must not be disabled)'; })()`
4952
5105
  );
4953
5106
  if (ok !== true) return { id: spec.id, pass: false, detail: `focus ${step.assertFocusable}: ${String(ok)}` };
5107
+ } else if ("assertRefReachesControl" in step) {
5108
+ const verdict = await page.evaluate(
5109
+ `(() => {
5110
+ const el = document.querySelector('#root ${step.assertRefReachesControl}');
5111
+ if (!el) return 'no element matches ${step.assertRefReachesControl} \u2014 the contract requires a native control';
5112
+ const probe = window.__tendrilRef;
5113
+ if (!probe || probe.current == null) return 'ref.current is null \u2014 the entry export must be a forwardRef component whose ref reaches the native control (a plain function component ignores the ref)';
5114
+ return probe.current === el ? true : 'ref.current is <' + String(probe.current.tagName).toLowerCase() + '> but the control is <' + el.tagName.toLowerCase() + '> \u2014 forward the ref to the control element itself, not a wrapper';
5115
+ })()`
5116
+ );
5117
+ if (verdict !== true) return { id: spec.id, pass: false, detail: `ref ${step.assertRefReachesControl}: ${String(verdict)}` };
4954
5118
  } else if ("assertAnimated" in step) {
4955
5119
  const animated = await page.evaluate(ANIMATED_PROBE);
4956
- if (animated !== true) return { id: spec.id, pass: false, detail: "no running animation anywhere in the subtree" };
5120
+ if (animated !== true) return { id: spec.id, pass: false, detail: "no running animation anywhere in the subtree \u2014 this pose must carry a LIVE animation (CSS animation, a Web Animation, or SVG <animate>); a static spinner image satisfies pixels but fails this check" };
4957
5121
  } else if ("type" in step) {
4958
5122
  const [sel, text] = step.type;
4959
5123
  await page.click(`#root ${sel}`, { timeout: 2e3 });
4960
5124
  await page.keyboard.type(text, { delay: 20 });
4961
5125
  await page.waitForTimeout(200);
5126
+ } else if ("typeChangesPixels" in step) {
5127
+ const [sel, text] = step.typeChangesPixels;
5128
+ await settle(page);
5129
+ const before = await shotRoot(page);
5130
+ await page.click(`#root ${sel}`, { timeout: 2e3 });
5131
+ await page.keyboard.type(text, { delay: 20 });
5132
+ await page.waitForTimeout(200);
5133
+ await freezeAnimations(page);
5134
+ const after = await shotRoot(page);
5135
+ if (before.equals(after)) {
5136
+ return {
5137
+ id: spec.id,
5138
+ pass: false,
5139
+ detail: `typing "${text}" into ${sel} changed nothing on screen \u2014 the typed value must be VISIBLY rendered (a hidden input that swallows typing while painted content stays fixed is a decoy, not a control)`
5140
+ };
5141
+ }
5142
+ } else if ("assertSpyFired" in step) {
5143
+ const fired = await page.evaluate(`(() => (window.__tendrilFired ?? {})[${JSON.stringify(step.assertSpyFired)}] === true)()`);
5144
+ if (fired !== true) {
5145
+ return {
5146
+ id: spec.id,
5147
+ pass: false,
5148
+ detail: `the caller's ${step.assertSpyFired} never fired \u2014 forward the caller's handler to the control (a stripped or overridden handler breaks every controlled consumer silently)`
5149
+ };
5150
+ }
5151
+ } else if ("assertDescribedByResolves" in step) {
5152
+ const [sel, expected] = step.assertDescribedByResolves;
5153
+ const verdict = await page.evaluate(
5154
+ `(() => {
5155
+ const el = document.querySelector('#root ${sel}');
5156
+ if (!el) return 'element missing';
5157
+ const ids = (el.getAttribute('aria-describedby') ?? '').trim().split(/\\s+/).filter(Boolean);
5158
+ if (ids.length === 0) return 'aria-describedby is missing/empty';
5159
+ const targets = ids.map((id) => document.getElementById(id));
5160
+ if (targets.some((t) => t === null)) return 'aria-describedby references an id that does not exist \u2014 a dangling idref is present, not wired';
5161
+ const text = targets.map((t) => (t && t.textContent) || '').join(' ');
5162
+ return text.includes(${JSON.stringify(expected)}) ? true : 'the referenced element(s) do not contain the message the prop supplied';
5163
+ })()`
5164
+ );
5165
+ if (verdict !== true) return { id: spec.id, pass: false, detail: `describedby on ${sel}: ${String(verdict)}` };
4962
5166
  } else if ("assertValue" in step) {
4963
5167
  const [sel, want] = step.assertValue;
4964
5168
  const value = await page.evaluate(`(() => { const el = document.querySelector('#root ${sel}'); return el ? el.value : 'MISSING'; })()`);
@@ -4972,7 +5176,7 @@ async function runSteps(page, spec, renderPose) {
4972
5176
  await page.keyboard.press("Tab");
4973
5177
  await page.waitForTimeout(200);
4974
5178
  const after = await page.screenshot();
4975
- if (Buffer.compare(before, after) === 0) return { id: spec.id, pass: false, detail: "Tab focus changes nothing (no focus ring)" };
5179
+ if (Buffer.compare(before, after) === 0) return { id: spec.id, pass: false, detail: "Tab focus changes nothing on screen \u2014 no visible focus indicator exists: style the control's :focus-visible state (shared declaration with its forced token) so keyboard focus paints the recorded ring" };
4976
5180
  } else if ("clickOutside" in step) {
4977
5181
  await page.mouse.click(870, 660);
4978
5182
  await page.waitForTimeout(200);
@@ -4985,9 +5189,9 @@ async function runSteps(page, spec, renderPose) {
4985
5189
  if (val !== want) return { id: spec.id, pass: false, detail: `${attr} on ${sel}: got ${String(val)}, want "${want}"` };
4986
5190
  } else if ("assertNotFocusable" in step) {
4987
5191
  const focused = await page.evaluate(
4988
- `(() => { const el = document.querySelector('#root ${step.assertNotFocusable}'); if (!el) return 'MISSING'; el.focus(); return document.activeElement === el; })()`
5192
+ `(() => { const els = [...document.querySelectorAll('#root ${step.assertNotFocusable}')]; if (els.length === 0) return 'MISSING'; for (const el of els) { el.focus(); if (document.activeElement === el) return true; } return false; })()`
4989
5193
  );
4990
- if (focused !== false) return { id: spec.id, pass: false, detail: focused === "MISSING" ? "element missing" : "disabled element took focus" };
5194
+ if (focused !== false) return { id: spec.id, pass: false, detail: focused === "MISSING" ? "element missing" : "disabled element took focus \u2014 use the native disabled attribute: pointer-events:none and aria-disabled leave the element focusable (Tab and .focus() still land); only disabled refuses focus" };
4991
5195
  } else if ("reducedMotionNoAnimation" in step) {
4992
5196
  await page.emulateMedia({ reducedMotion: "reduce" });
4993
5197
  await page.waitForTimeout(250);
@@ -5013,7 +5217,11 @@ async function runSteps(page, spec, renderPose) {
5013
5217
  await page.waitForTimeout(500);
5014
5218
  after = await page.screenshot();
5015
5219
  if (Buffer.compare(baseline, after) !== 0)
5016
- return { id: spec.id, pass: false, detail: "pixels differ after mouse click + mouse-away (ring bound to :focus instead of :focus-visible?)" };
5220
+ return {
5221
+ id: spec.id,
5222
+ pass: false,
5223
+ detail: "pixels differ after mouse click + mouse-away \u2014 something persisted from a pointer click: usually a focus ring bound to bare :focus (bind rings to :focus-visible; a mouse click must not leave one). If clicking THIS pose legitimately commits a state change, the check is aimed at the wrong pose (it must target one where clicking commits nothing) \u2014 report that rather than restyling"
5224
+ };
5017
5225
  }
5018
5226
  } else if ("assertNoReflow" in step) {
5019
5227
  const before = await page.evaluate("document.getElementById('probe').getBoundingClientRect().top");
@@ -5108,7 +5316,12 @@ async function runSteps(page, spec, renderPose) {
5108
5316
  await page.waitForTimeout(200);
5109
5317
  const after = await page.screenshot();
5110
5318
  await page.mouse.move(0, 0);
5111
- if (Buffer.compare(before, after) === 0) return { id: spec.id, pass: false, detail: `real :hover on ${step.hoverChangesPixels} changes nothing` };
5319
+ if (Buffer.compare(before, after) === 0)
5320
+ return {
5321
+ id: spec.id,
5322
+ pass: false,
5323
+ detail: `real :hover on ${step.hoverChangesPixels} changes nothing \u2014 the hover treatment must ride the REAL pseudo-class in a shared declaration with the forced token (e.g. :is(:hover, [data-tendril-state~="hover"])); a rule on the attribute selector alone never fires for a real pointer`
5324
+ };
5112
5325
  }
5113
5326
  } catch (err) {
5114
5327
  return { id: spec.id, pass: false, detail: `step failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
@@ -5119,10 +5332,10 @@ async function runSteps(page, spec, renderPose) {
5119
5332
  function recordingIsDark(task) {
5120
5333
  const rep = task.configs[0]?.rep;
5121
5334
  if (rep === void 0) return false;
5122
- const f = path17.join(task.set, rep, "get_screenshot.json");
5123
- if (!existsSync12(f)) return false;
5335
+ const f = path18.join(task.set, rep, "get_screenshot.json");
5336
+ if (!existsSync13(f)) return false;
5124
5337
  try {
5125
- const env = JSON.parse(readFileSync9(f, "utf8")).content.find((c) => c.type === "image");
5338
+ const env = JSON.parse(readFileSync10(f, "utf8")).content.find((c) => c.type === "image");
5126
5339
  if (env?.data === void 0) return false;
5127
5340
  const png = PNG2.sync.read(Buffer.from(env.data, "base64"));
5128
5341
  let sum = 0;
@@ -5172,6 +5385,10 @@ async function checkPrelude(page, task) {
5172
5385
  push(`touch-action(${sel})`, cs.touchAction === "manipulation", `"${cs.touchAction}" (want manipulation)`);
5173
5386
  push(`user-select(${sel})`, cs.userSelect === "none", `"${cs.userSelect}" (want none)`);
5174
5387
  }
5388
+ for (const sel of task.prelude.touchTargets ?? []) {
5389
+ const ta = await page.evaluate(`(() => { const el = document.querySelector('#root ${sel}'); return el ? getComputedStyle(el).touchAction : null; })()`);
5390
+ push(`touch-action(${sel})`, ta === "manipulation", ta === null ? "element missing" : `"${ta}" (want manipulation)`);
5391
+ }
5175
5392
  for (const sel of task.prelude.textInputs) {
5176
5393
  const us = await page.evaluate(`(() => { const el = document.querySelector('#root ${sel}'); return el ? (getComputedStyle(el).userSelect ?? getComputedStyle(el).webkitUserSelect) : null; })()`);
5177
5394
  push(`input-selectable(${sel})`, us !== null && us !== "none", us === null ? "element missing" : `input user-select "${us}" (must stay selectable)`);
@@ -5200,12 +5417,21 @@ async function checkBehaviors(task, bundleDir, opts = {}) {
5200
5417
  const results = [];
5201
5418
  let deadlined = false;
5202
5419
  try {
5203
- for (const spec of task.behaviors) {
5420
+ for (const rawSpec of task.behaviors) {
5421
+ const spec = JSON.parse(
5422
+ JSON.stringify(rawSpec).replaceAll(JSON.stringify("__TENDRIL_MINT__"), JSON.stringify(`t-${Math.random().toString(36).slice(2, 10)}`))
5423
+ );
5204
5424
  const cfg = task.configs.find((c) => c.rep === spec.config);
5205
5425
  if (cfg === void 0) {
5206
5426
  results.push({ id: spec.id, pass: false, detail: `unknown config ${spec.config}` });
5207
5427
  continue;
5208
5428
  }
5429
+ const cfgJson = JSON.stringify({
5430
+ component: cfg.component,
5431
+ props: { ...cfg.props, ...spec.props },
5432
+ ...spec.spyProps !== void 0 ? { spyProps: spec.spyProps } : {},
5433
+ ...spec.steps.some((s) => "assertRefReachesControl" in s) ? { refProbe: true } : {}
5434
+ });
5209
5435
  const html = `<!doctype html><html><head><meta charset="utf-8"><style>
5210
5436
  ${getFontFaces()}
5211
5437
  ${css}
@@ -5213,12 +5439,12 @@ ${TRANSITION_INSTANT_CSS}
5213
5439
  body{margin:0;padding:20px}
5214
5440
  #root{position:static}
5215
5441
  #probe{height:24px}
5216
- </style></head><body><div id="root"></div><div id="probe">reflow probe</div><script>window.__cfg=${JSON.stringify({ component: cfg.component, props: { ...cfg.props, ...spec.props }, ...spec.spyProps !== void 0 ? { spyProps: spec.spyProps } : {} })}</script><script>${js}</script></body></html>`;
5442
+ </style></head><body><div id="root"></div><div id="probe">reflow probe</div><script>window.__cfg=${cfgJson}</script><script>${js}</script></body></html>`;
5217
5443
  const renderPose = async (rep) => {
5218
5444
  const target = task.configs.find((c) => c.rep === rep);
5219
5445
  if (target === void 0) return { error: `unknown pose ${rep}` };
5220
5446
  const poseHtml = html.replace(
5221
- `window.__cfg=${JSON.stringify({ component: cfg.component, props: { ...cfg.props, ...spec.props }, ...spec.spyProps !== void 0 ? { spyProps: spec.spyProps } : {} })}`,
5447
+ `window.__cfg=${cfgJson}`,
5222
5448
  `window.__cfg=${JSON.stringify({ component: target.component, props: target.props })}`
5223
5449
  );
5224
5450
  const p = await browser.newPage({ viewport: { width: 900, height: 700 } });
@@ -5646,7 +5872,15 @@ function buildTrustStatement(input) {
5646
5872
  const unrecorded = input.latticeConfigs === null ? null : Math.max(0, input.latticeConfigs - input.scored);
5647
5873
  const interaction = input.interactionChecks === 0 ? "interaction behaviors NONE VERIFIED (0 checks)" : `interaction behaviors ${input.interactionPassed}/${input.interactionChecks}`;
5648
5874
  const prelude = input.preludeChecks === 0 ? "" : `, page hygiene ${input.preludePassed}/${input.preludeChecks}`;
5649
- return `Verified against recorded truth: ${input.pass}/${input.scored} recorded configs at or above the pass bar (${input.certified} certified), ${interaction}${prelude}.` + (unrecorded === null ? " Coverage denominator UNKNOWN (set predates lattice tracking): completeness is not established." : ` ${unrecorded} lattice configs are unrecorded and UNVERIFIED.`) + ` These numbers are claims: recompute them with \`tendril verify\` \u2014 certification authority lives in the CLI ruler, never in this file.`;
5875
+ const content = (input.contentChecks ?? 0) === 0 ? "" : `, content slots ${input.contentPassed}/${input.contentChecks}`;
5876
+ const parity = (input.parityChecks ?? 0) === 0 ? "" : `, state parity ${input.parityPassed}/${input.parityChecks}`;
5877
+ return `Verified against recorded truth: ${input.pass}/${input.scored} recorded configs at or above the pass bar (${input.certified} certified), ${interaction}${content}${parity}${prelude}.` + // No hardcoded REASON for the unknown denominator: a fresh
5878
+ // selection-scoped set is not "predating lattice tracking" (the
5879
+ // run-21 defect, fixed on the verify channel then and found still
5880
+ // live HERE by the 2026-08-16 sweep) — this artifact cannot see
5881
+ // the manifest, so it states the fact and points at verify, which
5882
+ // knows the reason.
5883
+ (unrecorded === null ? " Coverage denominator UNKNOWN (the set's full variant lattice was not established at plan time \u2014 `tendril verify` states why): completeness is not established." : ` ${unrecorded} lattice configs are unrecorded and UNVERIFIED.`) + ` These numbers are claims: recompute them with \`tendril verify\` \u2014 certification authority lives in the CLI ruler, never in this file.`;
5650
5884
  }
5651
5885
  function cssProvenanceComment(input) {
5652
5886
  const unrecorded = input.latticeConfigs === null ? "; coverage denominator unknown" : `; ${Math.max(0, input.latticeConfigs - input.scored)} lattice configs unverified`;
@@ -5674,7 +5908,12 @@ var init_bundle = __esm({
5674
5908
  name: z9.string(),
5675
5909
  type: z9.string(),
5676
5910
  required: z9.boolean(),
5677
- default: z9.string().optional()
5911
+ default: z9.string().optional(),
5912
+ /** Cycle D role-named slots ("error": presence drives the error
5913
+ * state; the public default is undefined). Declared so the field
5914
+ * SURVIVES the schema round-trip — zod was silently stripping it
5915
+ * from the public artifact (review). */
5916
+ role: z9.string().optional()
5678
5917
  });
5679
5918
  PropAdapterSchema = z9.record(
5680
5919
  z9.string(),
@@ -5791,8 +6030,53 @@ var init_src4 = __esm({
5791
6030
  });
5792
6031
 
5793
6032
  // packages/verify/src/bundle-quality.ts
5794
- import { readFileSync as readFileSync10, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync13 } from "node:fs";
5795
- import path18 from "node:path";
6033
+ import { readFileSync as readFileSync11, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync14 } from "node:fs";
6034
+ import path19 from "node:path";
6035
+ function expertLensFindings(entryFile, source, contract) {
6036
+ const findings = [];
6037
+ const lineOf = (index) => source.slice(0, index).split("\n").length;
6038
+ const spoof = /__tendril/i.exec(source);
6039
+ if (spoof !== null) {
6040
+ findings.push({
6041
+ kind: "expert-lens",
6042
+ file: entryFile,
6043
+ line: lineOf(spoof.index),
6044
+ message: "the bundle references a Tendril harness internal (__tendril*) \u2014 candidate code must never touch the measurement machinery; treat as a gaming attempt and inspect before trusting any verdict"
6045
+ });
6046
+ }
6047
+ const idx = /\[\s*key\s*:\s*string\s*\]\s*:/.exec(source);
6048
+ if (idx !== null) {
6049
+ findings.push({
6050
+ kind: "expert-lens",
6051
+ file: entryFile,
6052
+ line: lineOf(idx.index),
6053
+ message: "index-signature escape hatch ([key: string]) in the public props \u2014 extend the native control's prop surface (ComponentPropsWithoutRef) instead; the typed extension IS the rest-prop surface"
6054
+ });
6055
+ }
6056
+ if (contract.archetype !== "static") {
6057
+ if (contract.forwardRef && !/\bforwardRef\b/.test(source) && !/\bref\b/.test(source)) {
6058
+ findings.push({
6059
+ kind: "expert-lens",
6060
+ file: entryFile,
6061
+ message: "no ref path to the control (no forwardRef and no ref identifier) \u2014 control archetypes must let a consumer reach the native control (the ref-reaches-control check measures this)"
6062
+ });
6063
+ }
6064
+ for (const v of contract.derivedStateValues) {
6065
+ if ((contract.publicUnionValues ?? []).includes(v)) continue;
6066
+ const m = new RegExp(`\\w+\\?\\s*:[^;\\n]*"${v}"`).exec(source);
6067
+ if (m !== null) {
6068
+ findings.push({
6069
+ kind: "expert-lens",
6070
+ file: entryFile,
6071
+ line: lineOf(m.index),
6072
+ message: `a public prop's type carries the derived-state value "${v}" \u2014 the platform owns that state (focus/value/error semantics); derived states are never caller-set pose props`
6073
+ });
6074
+ break;
6075
+ }
6076
+ }
6077
+ }
6078
+ return findings;
6079
+ }
5796
6080
  function definedVars(tokensCss) {
5797
6081
  if (tokensCss === void 0) return void 0;
5798
6082
  const names = /* @__PURE__ */ new Set();
@@ -5801,19 +6085,19 @@ function definedVars(tokensCss) {
5801
6085
  }
5802
6086
  function recordedTokenMapState(setDir, reps) {
5803
6087
  const readMap = (file) => {
5804
- if (!existsSync13(file)) return void 0;
6088
+ if (!existsSync14(file)) return void 0;
5805
6089
  try {
5806
- const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync10(file, "utf8"))) || "{}");
6090
+ const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync11(file, "utf8"))) || "{}");
5807
6091
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
5808
6092
  } catch {
5809
6093
  return {};
5810
6094
  }
5811
6095
  };
5812
- const setLevel = readMap(path18.join(setDir, "get_variable_defs.json"));
6096
+ const setLevel = readMap(path19.join(setDir, "get_variable_defs.json"));
5813
6097
  if (setLevel !== void 0) return Object.keys(setLevel).length === 0 ? "empty" : "populated";
5814
6098
  let recorded = false;
5815
6099
  for (const rep of reps) {
5816
- const m = readMap(path18.join(setDir, rep, "get_variable_defs.json"));
6100
+ const m = readMap(path19.join(setDir, rep, "get_variable_defs.json"));
5817
6101
  if (m === void 0) continue;
5818
6102
  recorded = true;
5819
6103
  if (Object.keys(m).length > 0) return "populated";
@@ -5876,13 +6160,13 @@ function fontStackFindings(sheets, coverage) {
5876
6160
  }
5877
6161
  return findings;
5878
6162
  }
5879
- async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion) {
6163
+ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, contract) {
5880
6164
  const findings = [];
5881
- const entryPath = path18.join(bundleDir, entry);
5882
- const cssPath = path18.join(bundleDir, "styles.css");
5883
- const tokensPath = path18.join(bundleDir, "tokens.css");
5884
- const css = existsSync13(cssPath) ? readFileSync10(cssPath, "utf8") : "";
5885
- const tokensCss = existsSync13(tokensPath) ? readFileSync10(tokensPath, "utf8") : void 0;
6165
+ const entryPath = path19.join(bundleDir, entry);
6166
+ const cssPath = path19.join(bundleDir, "styles.css");
6167
+ const tokensPath = path19.join(bundleDir, "tokens.css");
6168
+ const css = existsSync14(cssPath) ? readFileSync11(cssPath, "utf8") : "";
6169
+ const tokensCss = existsSync14(tokensPath) ? readFileSync11(tokensPath, "utf8") : void 0;
5886
6170
  findings.push(
5887
6171
  ...fontStackFindings(
5888
6172
  [
@@ -5892,18 +6176,22 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion) {
5892
6176
  injectedFamilyWeights(fontManifest)
5893
6177
  )
5894
6178
  );
5895
- if (existsSync13(entryPath)) {
6179
+ if (existsSync14(entryPath)) {
5896
6180
  const workDir = newScratchDir("quality");
5897
6181
  try {
5898
- const tsxPath = path18.join(workDir, entry);
5899
- writeFileSync5(tsxPath, readFileSync10(entryPath, "utf8"));
6182
+ const tsxPath = path19.join(workDir, entry);
6183
+ writeFileSync5(tsxPath, readFileSync11(entryPath, "utf8"));
5900
6184
  for (const d of runTscStrict([tsxPath]).diagnostics) {
6185
+ if (d.code === 2307 && /['"]\.\/composed\//.test(d.message)) continue;
5901
6186
  findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
5902
6187
  }
5903
6188
  } finally {
5904
6189
  rmSync2(workDir, { recursive: true, force: true });
5905
6190
  }
5906
6191
  }
6192
+ if (contract !== void 0 && existsSync14(entryPath)) {
6193
+ findings.push(...expertLensFindings(entry, readFileSync11(entryPath, "utf8"), contract));
6194
+ }
5907
6195
  if (css !== "") {
5908
6196
  const mapState = set === void 0 ? void 0 : recordedTokenMapState(set.dir, set.reps);
5909
6197
  for (const v of (await runTokenLint(css, "styles.css", definedVars(tokensCss), mapState !== void 0 && mapState !== "populated")).violations) {
@@ -6021,8 +6309,8 @@ var init_effect_geometry = __esm({
6021
6309
  });
6022
6310
 
6023
6311
  // packages/verify/src/bundle-score.ts
6024
- import { existsSync as existsSync14, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
6025
- import path19 from "node:path";
6312
+ import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
6313
+ import path20 from "node:path";
6026
6314
  import { build as build4 } from "esbuild";
6027
6315
  import { chromium as chromium4 } from "playwright-core";
6028
6316
  function getFontFaces2() {
@@ -6076,7 +6364,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
6076
6364
  }
6077
6365
  function metadataRoot(set, rep) {
6078
6366
  try {
6079
- const text = JSON.parse(readFileSync11(path19.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6367
+ const text = JSON.parse(readFileSync12(path20.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6080
6368
  return parseMetadataStructure(text);
6081
6369
  } catch {
6082
6370
  return void 0;
@@ -6131,19 +6419,19 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
6131
6419
  });
6132
6420
  }
6133
6421
  function repMeta(set, rep) {
6134
- const text = JSON.parse(readFileSync11(path19.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6422
+ const text = JSON.parse(readFileSync12(path20.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
6135
6423
  const root = parseMetadataStructure(text);
6136
6424
  return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
6137
6425
  }
6138
6426
  function repRef(set, rep) {
6139
- const env = JSON.parse(readFileSync11(path19.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
6427
+ const env = JSON.parse(readFileSync12(path20.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
6140
6428
  return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
6141
6429
  }
6142
6430
  function repEffectExtents(set, rep) {
6143
- const file = path19.join(set, rep, "get_design_context.json");
6144
- if (!existsSync14(file)) return void 0;
6431
+ const file = path20.join(set, rep, "get_design_context.json");
6432
+ if (!existsSync15(file)) return void 0;
6145
6433
  try {
6146
- const text = JSON.parse(readFileSync11(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
6434
+ const text = JSON.parse(readFileSync12(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
6147
6435
  const extents = shadowExtents(text);
6148
6436
  return extents.top + extents.right + extents.bottom + extents.left > 0 ? extents : void 0;
6149
6437
  } catch {
@@ -6154,13 +6442,13 @@ async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
6154
6442
  if (opts.evidenceDir !== void 0) mkdirSync3(opts.evidenceDir, { recursive: true });
6155
6443
  const CONFIGS2 = task.configs;
6156
6444
  const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
6157
- const entryTsx = path19.join(bundleDir, task.entry);
6158
- if (!existsSync14(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
6445
+ const entryTsx = path20.join(bundleDir, task.entry);
6446
+ if (!existsSync15(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
6159
6447
  const css = candidateCss(bundleDir);
6160
6448
  const mountSrc = `
6161
6449
  import { createElement } from "react";
6162
6450
  import { createRoot } from "react-dom/client";
6163
- import * as B from ${JSON.stringify(path19.resolve(entryTsx))};
6451
+ import * as B from ${JSON.stringify(path20.resolve(entryTsx))};
6164
6452
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
6165
6453
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
6166
6454
  const root = document.getElementById("root");
@@ -6258,12 +6546,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
6258
6546
  return name === void 0 ? c : { ...c, name };
6259
6547
  });
6260
6548
  if (opts.evidenceDir !== void 0) {
6261
- writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
6262
- writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
6263
- writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
6549
+ writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
6550
+ writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
6551
+ writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref));
6264
6552
  for (const [i, c] of absent.slice(0, 3).entries()) {
6265
- writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
6266
- writeFileSync6(path19.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
6553
+ writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
6554
+ writeFileSync6(path20.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
6267
6555
  }
6268
6556
  }
6269
6557
  return {
@@ -6364,7 +6652,7 @@ var init_prelude = __esm({
6364
6652
  PRELUDE_CONTRACT = `BUNDLE PRELUDE (machine-verified on computed styles \u2014 the harness page provides NONE of this; your CSS must):
6365
6653
  - Containment reset on the component subtree: margin 0, box-sizing border-box, line-height/letter-spacing/font-family inherited from the root (host globals must not leak in).
6366
6654
  - Component root: -webkit-font-smoothing: antialiased and -moz-osx-font-smoothing: grayscale (recorded rasterization); font-synthesis: none (no faux bold or italic \u2014 text renders in the face your stack actually binds, so a weight the kit cannot serve reaches the score instead of being faked; it does NOT fall through to the next family in the stack); color-scheme MATCHING YOUR RECORDING (light for a light capture, dark for a dark one) and direction: ltr \u2014 pin them, so the render cannot follow the viewer OS preference and drift from the capture it is graded against; isolation: isolate (own stacking context; overlay z-indexes never fight the host).
6367
- - Interactive controls (buttons, options): touch-action: manipulation and user-select: none. Text inputs stay selectable (never user-select: none on them).
6655
+ - Interactive controls (buttons, options): touch-action: manipulation and user-select: none. Text inputs stay selectable (never user-select: none on them) \u2014 and never put user-select: none on an ancestor wrapping an editable control either: it inherits into the control; a text-entry component's root gets touch-action: manipulation only.
6368
6656
  - Scrollable popovers/menus: overscroll-behavior: contain.
6369
6657
  - Focus indicators bind to :focus-visible, never bare :focus. If the recording contains a focus pose, style the indicator from that recorded truth. If NO focus pose is recorded, do NOT invent ring colors/widths/offsets \u2014 an invented ring is unrecorded pixels; keep the browser's default indicator for keyboard focus and state the gap in your report. Text inputs match :focus-visible even on mouse click BY SPEC; the ONE permitted refinement is suppressing the indicator on a POSITIVELY OBSERVED pointer press, fail-safe toward showing it (programmatic focus, restored focus, and assistive tech all count as keyboard and keep the ring \u2014 WCAG 2.4.7 binds keyboard operation). Never suppress more broadly than an observed press, and never restyle what you kept.
6370
6658
  - Every animation wrapped in @media (prefers-reduced-motion: no-preference) or disabled under reduce.`;
@@ -6509,8 +6797,8 @@ var init_parity = __esm({
6509
6797
 
6510
6798
  // packages/verify/src/composition.ts
6511
6799
  import { createRequire as createRequire2 } from "node:module";
6512
- import { existsSync as existsSync15 } from "node:fs";
6513
- import path20 from "node:path";
6800
+ import { existsSync as existsSync16 } from "node:fs";
6801
+ import path21 from "node:path";
6514
6802
  import { build as build6 } from "esbuild";
6515
6803
  import { chromium as chromium7 } from "playwright-core";
6516
6804
  function getFontFaces4() {
@@ -6518,9 +6806,9 @@ function getFontFaces4() {
6518
6806
  return _fontFaces4;
6519
6807
  }
6520
6808
  async function compileInstrumentedMount(task, bundleDir) {
6521
- const entryTsx = path20.join(bundleDir, task.entry);
6522
- if (!existsSync15(entryTsx)) return { error: `${task.entry} missing` };
6523
- const requireFromVerify = createRequire2(path20.join(VERIFY_PKG_DIR, "package.json"));
6809
+ const entryTsx = path21.join(bundleDir, task.entry);
6810
+ if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
6811
+ const requireFromVerify = createRequire2(path21.join(VERIFY_PKG_DIR, "package.json"));
6524
6812
  let realJsxPath;
6525
6813
  try {
6526
6814
  realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
@@ -6531,7 +6819,7 @@ async function compileInstrumentedMount(task, bundleDir) {
6531
6819
  import { createElement } from "react";
6532
6820
  import { createRoot } from "react-dom/client";
6533
6821
  import { __registerParts } from "react/jsx-runtime";
6534
- import * as B from ${JSON.stringify(path20.resolve(entryTsx))};
6822
+ import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
6535
6823
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
6536
6824
  const pairs: Array<[unknown, string]> = [];
6537
6825
  for (const name of cfg.partComponents) {
@@ -6586,7 +6874,7 @@ function expectedParts(task, roles, mainSlug) {
6586
6874
  }
6587
6875
  function interiorRegions(setDir, roles) {
6588
6876
  const mains = roles.main;
6589
- const withInterior = mains.filter((m) => existsSync15(path20.join(setDir, m, "get_metadata_interior.json")));
6877
+ const withInterior = mains.filter((m) => existsSync16(path21.join(setDir, m, "get_metadata_interior.json")));
6590
6878
  if (withInterior.length === 0) {
6591
6879
  return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
6592
6880
  }
@@ -6802,17 +7090,17 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
6802
7090
  });
6803
7091
 
6804
7092
  // packages/verify/src/occlusion.ts
6805
- import { existsSync as existsSync16 } from "node:fs";
6806
- import path21 from "node:path";
7093
+ import { existsSync as existsSync17 } from "node:fs";
7094
+ import path22 from "node:path";
6807
7095
  import { build as build7 } from "esbuild";
6808
7096
  import { chromium as chromium8 } from "playwright-core";
6809
7097
  async function compileTwoUp(task, bundleDir) {
6810
- const entryTsx = path21.join(bundleDir, task.entry);
6811
- if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
7098
+ const entryTsx = path22.join(bundleDir, task.entry);
7099
+ if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
6812
7100
  const src = `
6813
7101
  import { createElement } from "react";
6814
7102
  import { createRoot } from "react-dom/client";
6815
- import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
7103
+ import * as B from ${JSON.stringify(path22.resolve(entryTsx))};
6816
7104
  const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
6817
7105
  const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
6818
7106
  for (const id of ["first", "second"]) {
@@ -6837,16 +7125,24 @@ for (const id of ["first", "second"]) {
6837
7125
  return { error: `does not compile: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` };
6838
7126
  }
6839
7127
  }
6840
- function overlayPlan(task) {
6841
- const openCfg = task.configs.find((c) => Object.entries(c.props).some(([k, v]) => v === true && /^(open|isopen|menuopen|expanded|isexpanded)$/i.test(k)));
6842
- if (openCfg !== void 0) return { cfg: openCfg, selector: OVERLAY_SELECTOR };
7128
+ function overlayPlan(task, authority) {
7129
+ const isOpen = ([k, v]) => v === true && /^(open|isopen|menuopen|expanded|isexpanded)$/i.test(k) || // Authoring's own exclusive-axis shape (`state: "open"`) never
7130
+ // matched the boolean-only rule even authority-sourced configs
7131
+ // unscheduled the gate (C2's second hole).
7132
+ typeof v === "string" && /^(open|expanded)$/i.test(v);
7133
+ const deciding = authority ?? task.configs;
7134
+ const openDecider = deciding.find((c) => Object.entries(c.props).some(isOpen));
7135
+ if (openDecider !== void 0) {
7136
+ const mounted = task.configs.find((c) => c.rep === openDecider.rep) ?? task.configs.find((c) => Object.entries(c.props).some(isOpen)) ?? openDecider;
7137
+ return { cfg: mounted, selector: OVERLAY_SELECTOR };
7138
+ }
6843
7139
  const pop = task.prelude.popover;
6844
7140
  const first = task.configs[0];
6845
7141
  if (pop !== void 0 && first !== void 0) return { cfg: first, trigger: pop.trigger, selector: pop.selector };
6846
7142
  return void 0;
6847
7143
  }
6848
7144
  async function checkSiblingOcclusion(task, bundleDir, css, opts = {}) {
6849
- const plan = overlayPlan(task);
7145
+ const plan = overlayPlan(task, opts.authority);
6850
7146
  if (plan === void 0) return [];
6851
7147
  const pop = { selector: plan.selector, trigger: plan.trigger };
6852
7148
  const cfg = plan.cfg;
@@ -6919,10 +7215,10 @@ body{margin:0;padding:16px;display:flex;flex-direction:column;gap:8px;align-item
6919
7215
  };
6920
7216
  })()`);
6921
7217
  if (verdict.kind === "no-menu") {
6922
- return [{ id: "sibling-overlay-hit-testable", pass: true, detail: `UNAVAILABLE: nothing matched "${pop.selector}" in the open config \u2014 occlusion was not measured` }];
7218
+ return [{ id: "sibling-overlay-hit-testable", pass: true, unmeasured: true, detail: `UNAVAILABLE: nothing matched "${pop.selector}" in the open config \u2014 occlusion was not measured` }];
6923
7219
  }
6924
7220
  if (verdict.kind === "no-overlap") {
6925
- return [{ id: "sibling-overlay-hit-testable", pass: true, detail: "not applicable: the open overlay does not overlap the next instance" }];
7221
+ return [{ id: "sibling-overlay-hit-testable", pass: true, unmeasured: true, detail: "not applicable: the open overlay does not overlap the next instance" }];
6926
7222
  }
6927
7223
  if (verdict.kind !== "menu") {
6928
7224
  return [
@@ -6996,23 +7292,23 @@ var init_src5 = __esm({
6996
7292
  });
6997
7293
 
6998
7294
  // packages/cli/src/environment.ts
6999
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
7000
- import path22 from "node:path";
7295
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
7296
+ import path23 from "node:path";
7001
7297
  import { createHash as createHash4 } from "node:crypto";
7002
7298
  import { fileURLToPath as fileURLToPath5 } from "node:url";
7003
7299
  function cliVersion() {
7004
7300
  try {
7005
- return JSON.parse(readFileSync13(path22.join(path22.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
7301
+ return JSON.parse(readFileSync14(path23.join(path23.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
7006
7302
  } catch {
7007
7303
  return "dev";
7008
7304
  }
7009
7305
  }
7010
7306
  function environmentStamp(taskFamilies) {
7011
- const manifestPath2 = path22.join(fontCacheDir(), "manifest.json");
7307
+ const manifestPath2 = path23.join(fontCacheDir(), "manifest.json");
7012
7308
  let fontsHash = null;
7013
- if (existsSync17(manifestPath2)) {
7309
+ if (existsSync18(manifestPath2)) {
7014
7310
  try {
7015
- const entries = JSON.parse(readFileSync13(manifestPath2, "utf8"));
7311
+ const entries = JSON.parse(readFileSync14(manifestPath2, "utf8"));
7016
7312
  const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
7017
7313
  const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
7018
7314
  fontsHash = faces.length === 0 ? null : createHash4("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
@@ -7056,8 +7352,8 @@ var init_describe = __esm({
7056
7352
  });
7057
7353
 
7058
7354
  // packages/cli/src/env.ts
7059
- import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
7060
- import path23 from "node:path";
7355
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
7356
+ import path24 from "node:path";
7061
7357
  function parseEnv(content) {
7062
7358
  const entries = /* @__PURE__ */ new Map();
7063
7359
  for (const line of content.split("\n")) {
@@ -7069,9 +7365,9 @@ function parseEnv(content) {
7069
7365
  function resolveCredential(name) {
7070
7366
  const fromProcess = process.env[name];
7071
7367
  if (fromProcess) return fromProcess;
7072
- const envPath = path23.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
7073
- if (!existsSync18(envPath)) return void 0;
7074
- return parseEnv(readFileSync14(envPath, "utf8")).get(name);
7368
+ const envPath = path24.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
7369
+ if (!existsSync19(envPath)) return void 0;
7370
+ return parseEnv(readFileSync15(envPath, "utf8")).get(name);
7075
7371
  }
7076
7372
  var init_env = __esm({
7077
7373
  "packages/cli/src/env.ts"() {
@@ -7131,17 +7427,17 @@ var init_output = __esm({
7131
7427
  });
7132
7428
 
7133
7429
  // packages/cli/src/entitlement.ts
7134
- import { chmodSync, existsSync as existsSync19, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
7430
+ import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
7135
7431
  import crypto from "node:crypto";
7136
7432
  import os4 from "node:os";
7137
- import path24 from "node:path";
7433
+ import path25 from "node:path";
7138
7434
  function entitlementPath() {
7139
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path24.join(os4.homedir(), ".tendril", "entitlement.json");
7435
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path25.join(os4.homedir(), ".tendril", "entitlement.json");
7140
7436
  }
7141
7437
  function readStoredEntitlement(file = entitlementPath()) {
7142
- if (!existsSync19(file)) return void 0;
7438
+ if (!existsSync20(file)) return void 0;
7143
7439
  try {
7144
- const parsed = JSON.parse(readFileSync15(file, "utf8"));
7440
+ const parsed = JSON.parse(readFileSync16(file, "utf8"));
7145
7441
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
7146
7442
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
7147
7443
  } catch {
@@ -7149,7 +7445,7 @@ function readStoredEntitlement(file = entitlementPath()) {
7149
7445
  }
7150
7446
  }
7151
7447
  function writeStoredEntitlement(stored, file = entitlementPath()) {
7152
- mkdirSync4(path24.dirname(file), { recursive: true });
7448
+ mkdirSync4(path25.dirname(file), { recursive: true });
7153
7449
  writeFileSync7(file, `${JSON.stringify(stored, null, 2)}
7154
7450
  `);
7155
7451
  chmodSync(file, 384);
@@ -7234,9 +7530,9 @@ var init_entitlement = __esm({
7234
7530
 
7235
7531
  // packages/cli/src/commands/doctor.ts
7236
7532
  import { spawnSync } from "node:child_process";
7237
- import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync6 } from "node:fs";
7533
+ import { existsSync as existsSync21, readFileSync as readFileSync17, readdirSync as readdirSync6 } from "node:fs";
7238
7534
  import os5 from "node:os";
7239
- import path25 from "node:path";
7535
+ import path26 from "node:path";
7240
7536
  function withDeadline(work, ms) {
7241
7537
  return Promise.race([
7242
7538
  work,
@@ -7296,17 +7592,17 @@ async function runDoctorChecks(options) {
7296
7592
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
7297
7593
  });
7298
7594
  }
7299
- const fontManifest = path25.join(fontCacheDir(), "manifest.json");
7595
+ const fontManifest = path26.join(fontCacheDir(), "manifest.json");
7300
7596
  checks.push(
7301
- existsSync20(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync16(fontManifest, "utf8")).length} faces)` } : {
7597
+ existsSync21(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync17(fontManifest, "utf8")).length} faces)` } : {
7302
7598
  name: "font-cache",
7303
7599
  ok: true,
7304
7600
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
7305
7601
  remediation: `Nothing to do now: \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` fetches exactly what a recording declares, and generate/verify name that command \u2014 with the set filled in \u2014 when they need it.`
7306
7602
  }
7307
7603
  );
7308
- const pluginRoot = path25.join(os5.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
7309
- if (existsSync20(pluginRoot)) {
7604
+ const pluginRoot = path26.join(os5.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
7605
+ if (existsSync21(pluginRoot)) {
7310
7606
  try {
7311
7607
  const versions = readdirSync6(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
7312
7608
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -8397,12 +8693,12 @@ __export(record_exports, {
8397
8693
  runRecordPlan: () => runRecordPlan,
8398
8694
  runRecordStatus: () => runRecordStatus
8399
8695
  });
8400
- import { existsSync as existsSync22, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, readdirSync as readdirSync8 } from "node:fs";
8696
+ import { existsSync as existsSync23, mkdtempSync as mkdtempSync2, readFileSync as readFileSync19, readdirSync as readdirSync8 } from "node:fs";
8401
8697
  import os6 from "node:os";
8402
- import path28 from "node:path";
8698
+ import path29 from "node:path";
8403
8699
  import { writeFileSync as writeFileSync9 } from "node:fs";
8404
8700
  function recordsInteractionState(reports) {
8405
- const evident = (s) => stateTokens(s).some((t) => INTERACTION_EVIDENCE_VALUES.has(t));
8701
+ const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
8406
8702
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
8407
8703
  }
8408
8704
  function interactionDisclosure(component, reports) {
@@ -8422,7 +8718,7 @@ function interactionDisclosure(component, reports) {
8422
8718
  };
8423
8719
  }
8424
8720
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
8425
- const env = JSON.parse(readFileSync18(file, "utf8"));
8721
+ const env = JSON.parse(readFileSync19(file, "utf8"));
8426
8722
  const text = env.content.map((c) => c.text ?? "").join("\n");
8427
8723
  const symbols = [];
8428
8724
  const walk2 = (node, ancestor) => {
@@ -8480,7 +8776,7 @@ function runRecordPlan(opts) {
8480
8776
  if (rawFile !== void 0) {
8481
8777
  try {
8482
8778
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
8483
- const tmp = path28.join(mkdtempSync2(path28.join(os6.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
8779
+ const tmp = path29.join(mkdtempSync2(path29.join(os6.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
8484
8780
  writeFileSync9(tmp, JSON.stringify(envelope));
8485
8781
  metadataEntries.push({ file: tmp });
8486
8782
  } catch (err) {
@@ -8502,7 +8798,7 @@ function runRecordPlan(opts) {
8502
8798
  let metadataTruncated = false;
8503
8799
  for (const { file, frame } of metadataEntries) {
8504
8800
  try {
8505
- const parsed = symbolsFromMetadataEnvelope(path28.resolve(file), frame);
8801
+ const parsed = symbolsFromMetadataEnvelope(path29.resolve(file), frame);
8506
8802
  symbols.push(...parsed.symbols);
8507
8803
  if (parsed.truncated) metadataTruncated = true;
8508
8804
  } catch (err) {
@@ -8536,7 +8832,7 @@ function runRecordPlan(opts) {
8536
8832
  if (symbols.length === 0) {
8537
8833
  const leads = metadataEntries.flatMap(({ file }) => {
8538
8834
  try {
8539
- const env = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
8835
+ const env = JSON.parse(readFileSync19(path29.resolve(file), "utf8"));
8540
8836
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
8541
8837
  } catch {
8542
8838
  return [];
@@ -8625,7 +8921,7 @@ function runRecordPlan(opts) {
8625
8921
  // a CLI flag and the MCP surface has no parameter for it.
8626
8922
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
8627
8923
  text: "Record a reduced pose set (anchor + one-factor sweeps + conflict crosses) instead of the full variant matrix. This buys calls with coverage \u2014 sampling is blind to multi-axis interactions and the report discloses the poses it skipped \u2014 so only the user may accept that trade. There is no tool parameter for it; the user runs it themselves in their own terminal, before any pose is recorded.",
8628
- userRuns: [`rm ${quoteArg(path28.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
8924
+ userRuns: [`rm ${quoteArg(path29.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
8629
8925
  },
8630
8926
  {
8631
8927
  id: "larger-allowance",
@@ -8795,7 +9091,7 @@ function runRecordNext(opts) {
8795
9091
  const progress = payload["progress"];
8796
9092
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
8797
9093
  \u2192 ${payload["note"]}
8798
- \u2192 then: ${tendrilCommand(`record ingest --set ${path28.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
9094
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path29.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
8799
9095
  `);
8800
9096
  });
8801
9097
  }
@@ -8869,7 +9165,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
8869
9165
  const skipped = [];
8870
9166
  const failed = [];
8871
9167
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
8872
- if (existsSync22(path28.join(setDir, rep, name))) {
9168
+ if (existsSync23(path29.join(setDir, rep, name))) {
8873
9169
  skipped.push(name);
8874
9170
  continue;
8875
9171
  }
@@ -8891,16 +9187,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
8891
9187
  }
8892
9188
  function rawEnvelopeFromFile(file, parts) {
8893
9189
  if (parts) {
8894
- const blocks = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
9190
+ const blocks = JSON.parse(readFileSync19(path29.resolve(file), "utf8"));
8895
9191
  if (!Array.isArray(blocks) || blocks.length === 0 || blocks.some((p) => typeof p !== "string")) throw new Error("parts file must be a non-empty JSON array of strings");
8896
9192
  return { content: blocks.map((text) => ({ type: "text", text })) };
8897
9193
  }
8898
- return { content: [{ type: "text", text: readFileSync18(path28.resolve(file), "utf8") }] };
9194
+ return { content: [{ type: "text", text: readFileSync19(path29.resolve(file), "utf8") }] };
8899
9195
  }
8900
9196
  async function runRecordIngest(opts) {
8901
9197
  let payload;
8902
9198
  try {
8903
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync18(path28.resolve(opts.file), "utf8"));
9199
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync19(path29.resolve(opts.file), "utf8"));
8904
9200
  } catch (err) {
8905
9201
  fail(opts, ExitCode.InputValidation, {
8906
9202
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -8912,7 +9208,7 @@ async function runRecordIngest(opts) {
8912
9208
  fail(opts, ExitCode.InputValidation, {
8913
9209
  error: "--raw is for text tool responses; screenshots are binary",
8914
9210
  code: "envelope-invalid",
8915
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
9211
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path29.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
8916
9212
  });
8917
9213
  }
8918
9214
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -8932,7 +9228,7 @@ async function runRecordIngest(opts) {
8932
9228
  remediation: REINGEST_GUIDANCE
8933
9229
  });
8934
9230
  }
8935
- writeFileSync9(path28.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
9231
+ writeFileSync9(path29.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
8936
9232
  `);
8937
9233
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
8938
9234
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -8956,7 +9252,7 @@ async function runRecordIngest(opts) {
8956
9252
  remediation: REINGEST_GUIDANCE
8957
9253
  });
8958
9254
  }
8959
- writeFileSync9(path28.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
9255
+ writeFileSync9(path29.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
8960
9256
  `);
8961
9257
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
8962
9258
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -8972,7 +9268,7 @@ async function runRecordIngest(opts) {
8972
9268
  if (assets !== void 0) {
8973
9269
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
8974
9270
  `);
8975
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
9271
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path29.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
8976
9272
  `);
8977
9273
  }
8978
9274
  });
@@ -9045,14 +9341,14 @@ async function runRecordIngestRep(opts) {
9045
9341
  if (assets !== void 0) {
9046
9342
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
9047
9343
  `);
9048
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
9344
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path29.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
9049
9345
  `);
9050
9346
  }
9051
9347
  });
9052
9348
  }
9053
9349
  function runRecordAsset(opts) {
9054
9350
  if (opts.dir !== void 0) {
9055
- const dir = path28.resolve(opts.dir);
9351
+ const dir = path29.resolve(opts.dir);
9056
9352
  const names = readdirSync8(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
9057
9353
  if (names.length === 0) {
9058
9354
  fail(opts, ExitCode.InputValidation, {
@@ -9064,7 +9360,7 @@ function runRecordAsset(opts) {
9064
9360
  const ingested = [];
9065
9361
  try {
9066
9362
  for (const name of names) {
9067
- ingestAsset(opts.setDir, opts.rep, name, readFileSync18(path28.join(dir, name)));
9363
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync19(path29.join(dir, name)));
9068
9364
  ingested.push(name);
9069
9365
  }
9070
9366
  } catch (err) {
@@ -9084,11 +9380,11 @@ function runRecordAsset(opts) {
9084
9380
  fail(opts, ExitCode.InputValidation, {
9085
9381
  error: "pass --name and --file for a single asset, or --dir for a batch",
9086
9382
  code: "asset-rejected",
9087
- remediation: tendrilCommand(`record asset --set ${path28.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
9383
+ remediation: tendrilCommand(`record asset --set ${path29.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
9088
9384
  });
9089
9385
  }
9090
9386
  try {
9091
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync18(path28.resolve(opts.file)));
9387
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync19(path29.resolve(opts.file)));
9092
9388
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
9093
9389
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
9094
9390
  `);
@@ -9143,7 +9439,7 @@ function narrowedRoles(derived, override) {
9143
9439
  function rolesFromFile(opts, file, derived) {
9144
9440
  let json;
9145
9441
  try {
9146
- json = JSON.parse(readFileSync18(path28.resolve(file), "utf8"));
9442
+ json = JSON.parse(readFileSync19(path29.resolve(file), "utf8"));
9147
9443
  } catch (err) {
9148
9444
  fail(opts, ExitCode.InputValidation, {
9149
9445
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -9181,11 +9477,11 @@ function rolesFromFile(opts, file, derived) {
9181
9477
  };
9182
9478
  }
9183
9479
  function runRecordFinish(opts) {
9184
- if (!existsSync22(path28.join(opts.setDir, "recording-set.json"))) {
9480
+ if (!existsSync23(path29.join(opts.setDir, "recording-set.json"))) {
9185
9481
  fail(opts, ExitCode.InputValidation, {
9186
9482
  error: `no recording-set.json in ${opts.setDir}`,
9187
9483
  code: "no-recording-set",
9188
- remediation: `Run \`${tendrilCommand(`record plan --set ${path28.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
9484
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path29.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
9189
9485
  });
9190
9486
  }
9191
9487
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -9213,17 +9509,17 @@ function runRecordFinish(opts) {
9213
9509
  fail(opts, ExitCode.ConfirmationRequired, {
9214
9510
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
9215
9511
  code: "roles-confirmation-not-interactive",
9216
- remediation: `A human runs \`${tendrilCommand(`record finish --set ${path28.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
9512
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path29.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
9217
9513
  });
9218
9514
  }
9219
9515
  const merged = { ...raw, roles };
9220
- const { issues } = validateRecordingSet(merged, (rel) => existsSync22(path28.join(opts.setDir, rel)));
9516
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync23(path29.join(opts.setDir, rel)));
9221
9517
  const errors = issues.filter((i) => i.severity === "error");
9222
9518
  if (errors.length > 0) {
9223
9519
  fail(opts, ExitCode.InputValidation, {
9224
9520
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
9225
9521
  code: "recording-set-invalid",
9226
- remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path28.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
9522
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path29.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
9227
9523
  });
9228
9524
  }
9229
9525
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -9270,6 +9566,74 @@ var init_record = __esm({
9270
9566
  }
9271
9567
  });
9272
9568
 
9569
+ // packages/generate/src/archetype.ts
9570
+ function deriveArchetype(signals) {
9571
+ if (signals.textEntry.length > 0) {
9572
+ return {
9573
+ archetype: "text-input",
9574
+ evidence: signals.textEntry,
9575
+ // textarea included: the recording cannot distinguish single-
9576
+ // from multi-line entry, and the stay-selectable/value/focus
9577
+ // contracts are identical for both.
9578
+ nativeControl: ":is(input, textarea)",
9579
+ derivedStates: { ...TEXT_INPUT_DERIVED }
9580
+ };
9581
+ }
9582
+ if (signals.selection) {
9583
+ return {
9584
+ archetype: "selectable-control",
9585
+ evidence: ["selection axis (boolean selected/checked prop)"],
9586
+ // The honest shapes for a selection control (Cycle C F11):
9587
+ // native input (radio/checkbox pattern) or an aria-pressed
9588
+ // button; select for listbox-shaped kits.
9589
+ nativeControl: ":is(input, button, select)",
9590
+ derivedStates: { ...CONTROL_DERIVED }
9591
+ };
9592
+ }
9593
+ const STRONG_ACTION_VALUES = /* @__PURE__ */ new Set(["hover", "hovered", "focus", "focused", "focus-visible", "active", "pressed", "press"]);
9594
+ const kebab5 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
9595
+ const strongEvidence = signals.interactionEvidence.some((e) => {
9596
+ const value = kebab5(e.split("=").slice(1).join("="));
9597
+ return STRONG_ACTION_VALUES.has(value) || value.split("-").some((t) => STRONG_ACTION_VALUES.has(t));
9598
+ });
9599
+ if (signals.forcedStates.length > 0 || strongEvidence) {
9600
+ return {
9601
+ archetype: "action-control",
9602
+ evidence: signals.forcedStates.length > 0 ? signals.forcedStates.map((t) => `forced state "${t}"`) : signals.interactionEvidence,
9603
+ // a[href] included (review): the brief's own root-size text
9604
+ // endorses an <a href> root for link shapes — demanding <button>
9605
+ // would fail the principal-level implementation of a link kit.
9606
+ nativeControl: ":is(button, a[href])",
9607
+ derivedStates: { ...CONTROL_DERIVED }
9608
+ };
9609
+ }
9610
+ return { archetype: "static", evidence: [], nativeControl: "", derivedStates: {} };
9611
+ }
9612
+ function derivedStateKind(canon, kebabValue) {
9613
+ return canon.derivedStates[kebabValue];
9614
+ }
9615
+ var TEXT_INPUT_DERIVED, CONTROL_DERIVED;
9616
+ var init_archetype = __esm({
9617
+ "packages/generate/src/archetype.ts"() {
9618
+ "use strict";
9619
+ TEXT_INPUT_DERIVED = {
9620
+ focus: "focus",
9621
+ focused: "focus",
9622
+ "focus-visible": "focus",
9623
+ filled: "value",
9624
+ typing: "value",
9625
+ placeholder: "value",
9626
+ error: "error-presence",
9627
+ invalid: "error-presence"
9628
+ };
9629
+ CONTROL_DERIVED = {
9630
+ focus: "focus",
9631
+ focused: "focus",
9632
+ "focus-visible": "focus"
9633
+ };
9634
+ }
9635
+ });
9636
+
9273
9637
  // packages/generate/src/engine.ts
9274
9638
  var init_engine = __esm({
9275
9639
  "packages/generate/src/engine.ts"() {
@@ -9457,8 +9821,8 @@ var init_engine_curated = __esm({
9457
9821
  });
9458
9822
 
9459
9823
  // packages/generate/src/loop.ts
9460
- import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync19, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
9461
- import path29 from "node:path";
9824
+ import { existsSync as existsSync24, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
9825
+ import path30 from "node:path";
9462
9826
  import { z as z12 } from "zod";
9463
9827
  function objective(scores, behaviors) {
9464
9828
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -9470,7 +9834,9 @@ function objective(scores, behaviors) {
9470
9834
  }
9471
9835
  function buildFeedback(scores, behaviors, bar, mode = "fenced") {
9472
9836
  const preludeLines = behaviors.filter((b) => !b.pass && b.id.startsWith("prelude:")).map((b) => `PRELUDE FAIL ${b.id}: ${b.detail ?? "failed"}`);
9473
- const behLines = behaviors.filter((b) => !b.pass && !b.id.startsWith("prelude:")).map((b) => `BEHAVIOR FAIL ${b.id}: ${b.detail ?? "failed"}`);
9837
+ const unmeasurable = behaviors.filter((b) => !b.pass && !b.id.startsWith("prelude:") && (b.detail?.startsWith("unmeasurable:") ?? false));
9838
+ const unmeasurableLines = unmeasurable.map((b) => `UNMEASURABLE ${b.id}: ${b.detail ?? "failed"}`);
9839
+ const behLines = behaviors.filter((b) => !b.pass && !b.id.startsWith("prelude:") && !unmeasurable.includes(b)).map((b) => `BEHAVIOR FAIL ${b.id}: ${b.detail ?? "failed"}`);
9474
9840
  const lines = scores.map((s) => {
9475
9841
  const base = `${s.pass ? "PASS" : "FAIL"} ${s.rep}: sim=${s.similarity} ink=${s.inkRecall}`;
9476
9842
  const err = s.error !== void 0 ? ` error=${s.error}` : "";
@@ -9484,7 +9850,10 @@ function buildFeedback(scores, behaviors, bar, mode = "fenced") {
9484
9850
  ${lines.join("\n")}${behLines.length > 0 ? `
9485
9851
 
9486
9852
  BEHAVIORAL invariants (machine-verified, GATING \u2014 fix these with real handlers/animation/cursor, do not fake pixels):
9487
- ${behLines.join("\n")}` : ""}${preludeLines.length > 0 ? `
9853
+ ${behLines.join("\n")}` : ""}${unmeasurableLines.length > 0 ? `
9854
+
9855
+ UNMEASURABLE checks (still failing, but read the cause BEFORE spending a round: each line says whether the blocker is your render \u2014 fixable, fix the named pose's paint \u2014 or the recording/at-rest motion, which NO component code can fix; in that case REPORT it and do not iterate on it):
9856
+ ${unmeasurableLines.join("\n")}` : ""}${preludeLines.length > 0 ? `
9488
9857
 
9489
9858
  PRELUDE parity (computed-style checks, GATING \u2014 fix the named CSS property on the named element):
9490
9859
  ${preludeLines.join("\n")}` : ""}${absentLines.length > 0 ? `
@@ -9495,9 +9864,9 @@ ${absentLines.join("\n")}` : ""}
9495
9864
  Fix the FAIL configs (region coordinates are in the recorded screenshot's frame; ink<1 means recorded foreground pixels your render does not cover). Reading the pair: ink credits a recorded pixel when ANY render ink lands within 1px of it, so ink=1 proves coverage \u2014 not presence, shape, or colour. A wrong-shaped mark over the right region, or a wrong-coloured one that is still ink, scores 1.000; and a recorded mark within ~30 channel-sum of the backdrop (#f6f6f6 on white is 27) is not ink at all, so it can be absent at ink 1.000 with no MISSING line. With ink=1 AND low sim, coverage held while pixels differ, so start with the TWO causes below \u2014 but never read ink=1 as "nothing is missing": confirm every faint or small recorded mark (hairlines, dividers, low-contrast controls) exists in your render. GEOMETRY: wrong position/size/radius; the diff shows shifted edges and bands. WRONG TEXT WEIGHT OR FACE: the diff shows a uniform haze over glyph runs; CSS binds the font FAMILY before the weight, a later family in the stack is never consulted for a weight the first one lacks, and font-synthesis: none rules out faux-bold \u2014 so a stack led by a family the kit holds at one weight renders every other weight in that face, silently. Check the font stack against the kit's cached faces before concluding geometry is at fault. LOW ink with high sim means recorded marks are absent or painted invisibly. Do not regress PASS configs. ${mode === "files" ? "Update the files in your candidate directory and re-run the score." : "Reply with the complete corrected files in the same FILE format."}`;
9496
9865
  }
9497
9866
  function archivePriorRun(outDir) {
9498
- if (!existsSync23(path29.join(outDir, "run-log.json")) && !existsSync23(path29.join(outDir, "loop-state.json"))) return void 0;
9867
+ if (!existsSync24(path30.join(outDir, "run-log.json")) && !existsSync24(path30.join(outDir, "loop-state.json"))) return void 0;
9499
9868
  let n = 1;
9500
- while (existsSync23(`${outDir}-prev-${n}`)) n += 1;
9869
+ while (existsSync24(`${outDir}-prev-${n}`)) n += 1;
9501
9870
  renameSync(outDir, `${outDir}-prev-${n}`);
9502
9871
  return `${outDir}-prev-${n}`;
9503
9872
  }
@@ -9506,14 +9875,14 @@ async function runEngineLoop(opts) {
9506
9875
  const plateau = opts.plateau ?? 2;
9507
9876
  const progress = opts.onProgress ?? (() => {
9508
9877
  });
9509
- const statePath = path29.join(opts.outDir, "loop-state.json");
9510
- const resuming = opts.resume === true && existsSync23(statePath);
9878
+ const statePath = path30.join(opts.outDir, "loop-state.json");
9879
+ const resuming = opts.resume === true && existsSync24(statePath);
9511
9880
  if (!resuming) {
9512
9881
  const archived = archivePriorRun(opts.outDir);
9513
9882
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
9514
9883
  }
9515
9884
  mkdirSync6(opts.outDir, { recursive: true });
9516
- const scratch = path29.join(opts.outDir, ".candidate");
9885
+ const scratch = path30.join(opts.outDir, ".candidate");
9517
9886
  let attempts = [];
9518
9887
  let log = [];
9519
9888
  let best;
@@ -9521,7 +9890,7 @@ async function runEngineLoop(opts) {
9521
9890
  let nonAccepted = 0;
9522
9891
  let stopReason = "max-iterations";
9523
9892
  if (resuming) {
9524
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync19(statePath, "utf8")));
9893
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync20(statePath, "utf8")));
9525
9894
  attempts = restored.attempts;
9526
9895
  log = restored.iterations;
9527
9896
  spentUsd = restored.spentUsd;
@@ -9541,7 +9910,7 @@ async function runEngineLoop(opts) {
9541
9910
  };
9542
9911
  const writeCandidate = (files) => {
9543
9912
  mkdirSync6(scratch, { recursive: true });
9544
- for (const [name, content] of Object.entries(files)) writeFileSync10(path29.join(scratch, name), content);
9913
+ for (const [name, content] of Object.entries(files)) writeFileSync10(path30.join(scratch, name), content);
9545
9914
  };
9546
9915
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
9547
9916
  writeCandidate(candidate.files);
@@ -9599,8 +9968,8 @@ async function runEngineLoop(opts) {
9599
9968
  const usd = candidate.usage?.usd ?? 0;
9600
9969
  spentUsd += usd;
9601
9970
  if (candidate.raw !== void 0) {
9602
- mkdirSync6(path29.join(opts.outDir, "responses"), { recursive: true });
9603
- writeFileSync10(path29.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
9971
+ mkdirSync6(path30.join(opts.outDir, "responses"), { recursive: true });
9972
+ writeFileSync10(path30.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
9604
9973
  }
9605
9974
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
9606
9975
  const finish = candidate.usage?.finishReason ?? "?";
@@ -9626,10 +9995,10 @@ async function runEngineLoop(opts) {
9626
9995
  }
9627
9996
  }
9628
9997
  }
9629
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path29.join(opts.outDir, name), content);
9998
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path30.join(opts.outDir, name), content);
9630
9999
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
9631
10000
  writeFileSync10(
9632
- path29.join(opts.outDir, "run-log.json"),
10001
+ path30.join(opts.outDir, "run-log.json"),
9633
10002
  `${JSON.stringify(
9634
10003
  {
9635
10004
  ...opts.meta,
@@ -9696,8 +10065,9 @@ var init_loop2 = __esm({
9696
10065
  });
9697
10066
 
9698
10067
  // packages/generate/src/brief.ts
9699
- import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
9700
- import path30 from "node:path";
10068
+ import { existsSync as existsSync25, readFileSync as readFileSync21 } from "node:fs";
10069
+ import path31 from "node:path";
10070
+ import { PNG as PNG3 } from "pngjs";
9701
10071
  function singleAxes2(name) {
9702
10072
  const parsed = parseVariantAxes(name);
9703
10073
  if (parsed === void 0) return void 0;
@@ -9737,13 +10107,50 @@ function authorComponentApi(opts) {
9737
10107
  if (/^(is)?(selected|checked)$/i.test(camel(key))) interactionEvidence.push(`${key} (selection axis)`);
9738
10108
  for (const v of domains.get(key)) {
9739
10109
  const k = kebab3(v);
9740
- if (INTERACTION_EVIDENCE_VALUES.has(k) || k.split("-").some((t) => INTERACTION_EVIDENCE_VALUES.has(t))) {
10110
+ const engaged = ENGAGED_STATE_VALUES.has(k) && kebab3(key) === "state";
10111
+ const classic = (t) => INTERACTION_EVIDENCE_VALUES.has(t) && !ENGAGED_STATE_VALUES.has(t);
10112
+ if (engaged || classic(k) || k.split("-").some(classic)) {
9741
10113
  interactionEvidence.push(`${key}=${v}`);
9742
10114
  }
9743
10115
  }
9744
10116
  }
10117
+ const TEXT_ENTRY_STATE_VALUES = /* @__PURE__ */ new Set(["typing", "filled", "placeholder"]);
10118
+ const TEXT_ENTRY_AXIS_NAMES = /* @__PURE__ */ new Set(["typing", "text-entered"]);
10119
+ const textEntry = [];
10120
+ for (const key of axisKeys) {
10121
+ const domain = domains.get(key);
10122
+ if (TEXT_ENTRY_AXIS_NAMES.has(kebab3(key)) && domain.every((v) => ["true", "false"].includes(kebab3(v)))) {
10123
+ textEntry.push(`${key} (boolean axis)`);
10124
+ }
10125
+ if (kebab3(key) !== "state") continue;
10126
+ if ((opts.textSlots ?? []).length === 0) continue;
10127
+ for (const v of domain) if (TEXT_ENTRY_STATE_VALUES.has(kebab3(v))) textEntry.push(`${key}=${v}`);
10128
+ }
10129
+ const preForcedStates = axisKeys.filter(isStateAxis).flatMap((key) => domains.get(key).map(kebab3).filter((k) => INTERACTION_STATES.has(k)));
10130
+ const archetype = deriveArchetype({
10131
+ textEntry,
10132
+ selection: interactionEvidence.some((e) => e.endsWith(" (selection axis)")),
10133
+ forcedStates: preForcedStates,
10134
+ interactionEvidence
10135
+ });
10136
+ const stateAxis = axisKeys.find(isStateAxis);
10137
+ const errorReps = new Set(
10138
+ stateAxis === void 0 ? [] : axed.filter((p) => derivedStateKind(archetype, kebab3(p.axes[stateAxis])) === "error-presence").map((p) => p.slug)
10139
+ );
10140
+ const errorSourceSlot = (opts.textSlots ?? []).find((s) => s.visibleIn.length > 0 && s.visibleIn.every((r) => errorReps.has(r)));
10141
+ const errorViaProp = errorSourceSlot !== void 0;
10142
+ const valueReps = new Set(
10143
+ stateAxis === void 0 ? [] : axed.filter((p) => {
10144
+ const k = kebab3(p.axes[stateAxis]);
10145
+ return derivedStateKind(archetype, k) === "value" && k !== "placeholder";
10146
+ }).map((p) => p.slug)
10147
+ );
10148
+ const valueSourceSlot = (opts.textSlots ?? []).find((s) => s !== errorSourceSlot && s.visibleIn.length > 0 && s.visibleIn.every((r) => valueReps.has(r)));
10149
+ const valueViaProp = valueSourceSlot !== void 0;
10150
+ const derivedPoseProps = {};
9745
10151
  const props = [];
9746
10152
  const forcedStates = [];
10153
+ const derivedStates = [];
9747
10154
  const syntheticCombos = [];
9748
10155
  const propNameFor = (axis) => axisPropName(opts.component, axis);
9749
10156
  for (const key of axisKeys) {
@@ -9760,6 +10167,13 @@ function authorComponentApi(opts) {
9760
10167
  } else if (BOOLEAN_STATES.has(k)) {
9761
10168
  props.push({ name: k, kind: "boolean" });
9762
10169
  splitBooleans.push(k);
10170
+ } else if (derivedStateKind(archetype, k) !== void 0) {
10171
+ const kind = derivedStateKind(archetype, k);
10172
+ if (kind === "error-presence" && errorViaProp || kind === "value" && valueViaProp && k !== "placeholder") {
10173
+ } else {
10174
+ if (!forcedStates.includes(k)) forcedStates.push(k);
10175
+ if (!derivedStates.includes(k)) derivedStates.push(k);
10176
+ }
9763
10177
  } else {
9764
10178
  unionValues.push(kebab3(v));
9765
10179
  }
@@ -9777,14 +10191,19 @@ function authorComponentApi(opts) {
9777
10191
  props.push({ name: propNameFor(key), kind: "union", values: ordered.map(kebab3), default: kebab3(def) });
9778
10192
  }
9779
10193
  }
9780
- const slots = (opts.textSlots ?? []).map((slot) => {
9781
- let name = slot.prop;
9782
- if (name !== "children" && (props.some((pr) => pr.name === name) || RESERVED_PROPS.has(name.toLowerCase()))) name = `${name}Text`;
10194
+ const slotRoles = /* @__PURE__ */ new Map();
10195
+ const slots = (opts.textSlots ?? []).filter((slot) => slot !== valueSourceSlot).map((slot) => {
10196
+ let name = slot === errorSourceSlot ? "error" : slot.prop;
10197
+ if (name !== "children" && (props.some((pr) => pr.name === name) || RESERVED_PROPS.has(name.toLowerCase()) || NATIVE_ATTR_COLLISIONS.has(name.toLowerCase()))) name = `${name}Text`;
9783
10198
  let n = 2;
9784
10199
  while (props.some((pr) => pr.name === name)) name = `${slot.prop}Text${n++}`;
10200
+ if (slot === errorSourceSlot) slotRoles.set(name, "error");
9785
10201
  return { ...slot, prop: name };
9786
10202
  });
9787
- for (const slot of slots) props.push({ name: slot.prop, kind: "string", default: slot.default });
10203
+ for (const slot of slots) {
10204
+ const role = slotRoles.get(slot.prop);
10205
+ props.push({ name: slot.prop, kind: "string", default: slot.default, ...role !== void 0 ? { role } : {} });
10206
+ }
9788
10207
  const configs = [];
9789
10208
  const seenAssignments = /* @__PURE__ */ new Map();
9790
10209
  const componentIdent = pascal(opts.component);
@@ -9796,9 +10215,18 @@ function authorComponentApi(opts) {
9796
10215
  if (v === defaults.get(key)) continue;
9797
10216
  const k = kebab3(v);
9798
10217
  if (isStateAxis(key)) {
9799
- if (INTERACTION_STATES.has(k)) tokens.push(k);
10218
+ const derived = derivedStateKind(archetype, k);
10219
+ const viaProp = derived === "error-presence" && errorViaProp || derived === "value" && valueViaProp && k !== "placeholder";
10220
+ if (INTERACTION_STATES.has(k) || derived !== void 0 && !viaProp) tokens.push(k);
9800
10221
  else if (BOOLEAN_STATES.has(k)) assignment[k] = true;
9801
- else assignment[propNameFor(key)] = k;
10222
+ else if (derived === "value" && viaProp) {
10223
+ const text = valueSourceSlot.overrides[p.slug] ?? valueSourceSlot.default;
10224
+ assignment["defaultValue"] = text;
10225
+ derivedPoseProps[k] ??= { defaultValue: text };
10226
+ } else if (derived === "error-presence" && viaProp) {
10227
+ const name = [...slotRoles.entries()].find(([, r]) => r === "error")?.[0];
10228
+ if (name !== void 0) derivedPoseProps[k] ??= { [name]: errorSourceSlot.overrides[p.slug] ?? errorSourceSlot.default };
10229
+ } else if (derived === void 0) assignment[propNameFor(key)] = k;
9802
10230
  } else if (["true", "false"].includes(k)) {
9803
10231
  assignment[propNameFor(key)] = k === "true";
9804
10232
  } else {
@@ -9809,6 +10237,7 @@ function authorComponentApi(opts) {
9809
10237
  for (const slot of slots) {
9810
10238
  const v = slot.overrides[p.slug];
9811
10239
  if (v !== void 0) assignment[slot.prop] = v;
10240
+ else if (slotRoles.get(slot.prop) === "error" && errorReps.has(p.slug)) assignment[slot.prop] = slot.default;
9812
10241
  }
9813
10242
  const canonical = JSON.stringify(Object.fromEntries(Object.entries(assignment).sort(([a], [b]) => a.localeCompare(b))));
9814
10243
  const clash = seenAssignments.get(canonical);
@@ -9827,6 +10256,8 @@ function authorComponentApi(opts) {
9827
10256
  props.push({ name: "onDismiss", kind: "callback", default: opts.dismissible });
9828
10257
  }
9829
10258
  const entry = `${componentIdent}.tsx`;
10259
+ const extendsNative = archetype.archetype === "text-input" ? "input" : archetype.archetype === "selectable-control" ? "input" : archetype.archetype === "action-control" ? "button" : "div";
10260
+ const isControl = archetype.archetype !== "static";
9830
10261
  const apiPin = {
9831
10262
  name: componentIdent,
9832
10263
  props: props.map((pr) => ({
@@ -9834,14 +10265,19 @@ function authorComponentApi(opts) {
9834
10265
  type: pr.kind === "boolean" ? "boolean" : pr.kind === "string" ? "string" : pr.kind === "callback" ? "() => void" : pr.values.map((v) => `"${v}"`).join(" | "),
9835
10266
  required: false,
9836
10267
  // A callback's `default` field carries detection EVIDENCE for the
9837
- // prop line, not a value — it never enters the pin.
9838
- ...pr.default !== void 0 && pr.kind !== "callback" ? { default: pr.default } : {}
10268
+ // prop line, not a value — it never enters the pin. An
10269
+ // error-role prop's public default is UNDEFINED (presence
10270
+ // semantics) — the recorded message is prose, never a default.
10271
+ ...pr.default !== void 0 && pr.kind !== "callback" && pr.role !== "error" ? { default: pr.default } : {},
10272
+ ...pr.role !== void 0 ? { role: pr.role } : {}
9839
10273
  })),
9840
10274
  forcedStates,
9841
- poseCompleteness: { recordedPoses: opts.poses.length, expressible: configs.length }
10275
+ poseCompleteness: { recordedPoses: opts.poses.length, expressible: configs.length },
10276
+ archetype: archetype.archetype,
10277
+ contract: { extendsNative, forwardRef: isControl, restPropsTo: isControl ? "control" : "root" }
9842
10278
  };
9843
10279
  const propLines = props.map(
9844
- (pr) => pr.kind === "boolean" ? ` ${pr.name}?: boolean;` : pr.kind === "string" ? ` ${pr.name}?: string; // CONTENT \u2014 recorded default ${JSON.stringify(pr.default)}` : pr.kind === "callback" ? ` ${pr.name}?: () => void; // NOTIFICATION \u2014 recorded evidence: ${pr.default ?? "the recorded affordance"}; the component owns its state (it hides/updates itself) and the callback informs, never controls` : ` ${pr.name}?: ${pr.values.map((v) => `"${v}"`).join(" | ")}; // default "${pr.default}"`
10280
+ (pr) => pr.kind === "boolean" ? ` ${pr.name}?: boolean;` : pr.role === "error" ? ` ${pr.name}?: string; // ERROR-AS-PRESENCE \u2014 default undefined (no error); when SET the component enters its error state: render the message, set aria-invalid on the control and aria-describedby pointing at the message element (machine-checked). Recorded message: ${JSON.stringify(pr.default)}` : pr.kind === "string" ? ` ${pr.name}?: string; // CONTENT \u2014 recorded default ${JSON.stringify(pr.default)}` : pr.kind === "callback" ? ` ${pr.name}?: () => void; // NOTIFICATION \u2014 recorded evidence: ${pr.default ?? "the recorded affordance"}; the component owns its state (it hides/updates itself) and the callback informs, never controls` : ` ${pr.name}?: ${pr.values.map((v) => `"${v}"`).join(" | ")}; // default "${pr.default}"`
9845
10281
  );
9846
10282
  const provided = opts.fonts ?? [];
9847
10283
  const recorded = opts.recordedFonts ?? [];
@@ -9853,23 +10289,43 @@ function authorComponentApi(opts) {
9853
10289
  }).filter((f) => f.missing.length > 0);
9854
10290
  const weightsLine = (opts.providedFaces ?? []).length > 0 ? `Weights the harness can actually serve: ${(opts.providedFaces ?? []).map((f) => `'${f.family}' (${[...f.weights].sort((a, b) => a - b).join(", ")})`).join("; ")}. ` + (faceGaps.length > 0 ? `WEIGHT GAP \u2014 ${faceGaps.map((g) => `'${g.family}' is recorded at ${g.missing.join(", ")} which the cache does NOT hold`).join("; ")}. CSS binds the FAMILY before the weight and font-synthesis: none blocks the fall-through, so text at a missing weight renders in a weight this family DOES have, silently and at full score-cost. Do not lead a font stack with a family that cannot serve the weight you are setting; put a family that has it first, or report the gap rather than chasing the difference as geometry. ` : "") : "";
9855
10291
  const fontsLine = weightsLine + (provided.length > 0 ? `Fonts provided by the harness: ${provided.map((f) => `'${f}'`).join(", ")}. ` : "") + (recorded.length > 0 ? `The recording's declared famil${recorded.length === 1 ? "y is" : "ies are"} ${recorded.map((f) => `'${f}'`).join(", ")}${unprovided.length > 0 ? ` \u2014 ${unprovided.map((f) => `'${f}'`).join(", ")} ${unprovided.length === 1 ? "is" : "are"} NOT provided: the mount will substitute a provided face, scoring reflects the recorded one, and the residual glyph delta is not closable from CSS \u2014 do not chase it` : ""}. ` : "");
9856
- const forcingCanon = forcedStates.length > 0 ? `Interactive states are REAL (:hover, :focus-visible) AND statically forceable via the data-tendril-state attribute (a DOCUMENTED token-list attribute; tokens: ${forcedStates.map((t) => `"${t}"`).join(" | ")}) spread onto the root; forced and real selectors must share ONE declaration block, e.g. :is(:hover, [data-tendril-state~="hover"]) \u2014 match with ~= so compound states ("hover selected") work. ` : "";
9857
- const systemApi = `Prescribed API (the harness mounts exactly this; deviation scores 0):
9858
- export function ${componentIdent}(props: {
10292
+ const forcingCanon = forcedStates.length > 0 ? `Interactive states are REAL (:hover, :focus-visible) AND statically forceable via the data-tendril-state attribute (a DOCUMENTED token-list attribute; tokens: ${forcedStates.map((t) => `"${t}"`).join(" | ")}) ${isControl ? "which arrives through the rest props and therefore lands on the NATIVE CONTROL \u2014 pair each forced token with its real condition on the control, and use :has() on ancestors where the recorded treatment paints outside it (the pinned Chromium supports :has())" : "spread onto the root"}; forced and real selectors must share ONE declaration block, e.g. :is(:hover, [data-tendril-state~="hover"]) \u2014 match with ~= so compound states ("hover selected") work. ` + // F12 (run-22 cycle): a kit that records "Focus" forces via a
10293
+ // "focus" token, but the prelude binds indicators to
10294
+ // :focus-visible, never bare :focus — left unsaid, the shared-
10295
+ // declaration example above points the generator straight at
10296
+ // the selector the prelude forbids.
10297
+ (forcedStates.includes("focus") ? `The recorded "focus" pose is KEYBOARD-focus treatment: pair its forced token with :focus-visible \u2014 :is(:focus-visible, [data-tendril-state~="focus"]) \u2014 never bare :focus (the prelude binds focus indicators to :focus-visible). ` : "") + // Cycle D (Output doctrine): derived states ride the SAME hook
10298
+ // but their real-use condition is platform state, not a prop —
10299
+ // the brief must name each pairing or the generator reinvents
10300
+ // the pose switchboard to satisfy the configs.
10301
+ (derivedStates.length > 0 ? `DERIVED STATES \u2014 ${derivedStates.map((t) => `"${t}"`).join(", ")} are states the PLATFORM owns; they are NEVER public props (no state/pose props in your API). In real use: focused comes from real focus (:focus-visible on the control or :focus-within on the root); filled/typing come from the control's VALUE (:not(:placeholder-shown), or state your component derives from its own value); placeholder is the empty-value look; error comes from your error/invalid semantics (aria-invalid). The forced token is the harness's STATIC channel for those poses \u2014 share each declaration between the real condition and its token, exactly like the interaction states above. ` : "") : "";
10302
+ const refElement = archetype.archetype === "text-input" ? "HTMLInputElement" : archetype.archetype === "selectable-control" ? "HTMLInputElement" : "HTMLButtonElement";
10303
+ const refElementNote = archetype.archetype === "text-input" ? " /* HTMLTextAreaElement if you build with a textarea */" : archetype.archetype === "selectable-control" ? " /* or HTMLButtonElement/HTMLSelectElement \u2014 match the control you pick */" : " /* HTMLAnchorElement for link shapes */";
10304
+ const signatureBlock = isControl ? `export const ${componentIdent} = forwardRef<${refElement}${refElementNote}, ${componentIdent}Props>(function ${componentIdent}(props, ref) { /* your implementation */ })
10305
+ interface ${componentIdent}Props extends ComponentPropsWithoutRef<"${extendsNative}"${archetype.archetype === "text-input" ? ' /* or "textarea" \u2014 whichever native control you honestly build with */' : archetype.archetype === "selectable-control" ? ' /* or "button"/"select" \u2014 whichever native control you honestly build with; the contract binds to the one you pick */' : ' /* or "a" for link shapes */'}> {
9859
10306
  ${propLines.join("\n")}
9860
- [key: string]: unknown; // MUST spread unknown props (incl. data-*) onto the root element
9861
- })
10307
+ }
10308
+ CONTRACT (the expert surface, part of the prescription): NO index-signature escape hatch ([key: string]: unknown is FORBIDDEN) \u2014 the native extension IS the rest-prop surface. Every prop not declared above (data-*, aria-*, native event handlers) forwards to the native control element, and the forwarded ref reaches that SAME element (import forwardRef and the ComponentPropsWithoutRef type from react; React-19 ref-as-prop is equally valid \u2014 the check binds ref.current to the control element, not to the mechanism). States the platform owns are DERIVED, never caller-set: no state/pose props beyond the interface above.` : `export function ${componentIdent}(props: ${componentIdent}Props)
10309
+ interface ${componentIdent}Props extends ComponentPropsWithoutRef<"div"> {
10310
+ ${propLines.join("\n")}
10311
+ }
10312
+ CONTRACT: no index-signature escape hatch \u2014 the native extension is the rest-prop surface; undeclared props (incl. data-*) spread onto the root element.`;
10313
+ const systemApi = `Prescribed API (the harness mounts exactly this; deviation scores 0):
10314
+ ${signatureBlock}
9862
10315
 
9863
10316
  ${syntheticCombos.length > 0 ? `UNRECORDED REACHABLE COMBINATIONS: the design's exclusive axis cannot express ${syntheticCombos.join(", ")} \u2014 the API split makes them reachable with NO recorded truth. Compose them from the recorded per-axis truth (paint variables: one axis sets, the other consumes), never invent a bespoke look, and list them in your report.
9864
- ` : ""}${slots.length > 0 ? `CONTENT PROPS: every string prop defaults to its RECORDED text \u2014 render the PROP, never a hardcoded literal. Configs pass per-pose recorded strings wherever the recording varies, and pixels enforce them: a hardcoded string fails those configs. Content presence (a heading that only exists in some poses) follows the AXIS props; the string prop only supplies the text. RICH-TEXT DEFAULTS: when the recorded content is a multi-run rich node (mixed weights, an underlined link span) whose concatenation is the prop's default string, a plain-string default would flatten the recorded formatting \u2014 default the parameter to undefined and render the recorded rich markup when the prop is absent; a caller-supplied string then renders plainly. That mirrors the design tool's own emission logic and keeps every recorded pose pixel-exact.
10317
+ ` : ""}${slots.length > 0 ? `CONTENT PROPS: every string prop defaults to its RECORDED text \u2014 render the PROP, never a hardcoded literal. Configs pass per-pose recorded strings wherever the recording varies, and pixels enforce them: a hardcoded string fails those configs. Content presence (a heading that only exists in some poses) follows the AXIS props; the string prop only supplies the text. RICH-TEXT DEFAULTS: when the recorded content is a multi-run rich node (mixed weights, an underlined link span) whose concatenation is the prop's default string, a plain-string default would flatten the recorded formatting \u2014 default the parameter to undefined and render the recorded rich markup when the prop is absent; a caller-supplied string then renders plainly. That mirrors the design tool's own emission logic and keeps every recorded pose pixel-exact.${slots.some((s) => s.richIn.length > 0) ? ` RICH POSES ARE NEVER PROP-FED: poses whose recorded slot content is itself a multi-run rich node do NOT receive the string prop from the harness (a plain string cannot reproduce the recorded formatting) \u2014 when the prop is absent, render THAT pose's recorded rich markup, keyed off the pose's own axis props; a caller-supplied string still renders plainly.` : ""}
10318
+ ` : ""}${valueViaProp ? `VALUE POSES RIDE THE NATIVE SURFACE: the recorded value text for filled/typing poses mounts through defaultValue on your control (the rest props carry it \u2014 never intercept it into a custom prop). Derive value-state styling from the control's own value (:not(:placeholder-shown), or state you track from the control's change events) \u2014 never from a pose prop and never by branching on the forcing attribute.
9865
10319
  ` : ""}Rules: generated element ids come from React's useId() \u2014 never Math.random() in render/state init (SSR hydration hazard; measured: two same-day bundles disagreed). Plain CSS in styles.css (tokens.css optional, loaded first) \u2014 no Tailwind, no imports beyond react/react-dom, and NEVER import the stylesheet from the entry module: the harness injects tokens.css and styles.css itself, and an entry that imports CSS does not compile here (measured cost: one full round, every config 0). The component renders the RECORDED content as its defaults \u2014 reproduce it from the emissions. ${forcingCanon}${fontsLine}ROOT SIZE IS YOURS AND PIXELS DO NOT CHECK IT, IN EITHER DIRECTION: the mount floors your root to the recorded box (#root > *{min-width:<recorded w>px;min-height:<recorded h>px} \u2014 a floor, never a cap), and the image the scorer compares is a CROP of that box, so paint outside it is never compared at all. Measured through the real scorer against a correct 120x40 root: an UNDERSIZED 80x28 root and OVERSIZED 160x52 and 124x44 roots ALL scored sim 1.000 / ink 1.000 with a byte-identical crop, as long as internals stay literal and top-left anchored. An oversized root shows only when its own border, radius or shadow \u2014 or a re-centred internal layout \u2014 lands back inside the crop. And some roots are not floored at all: a non-replaced INLINE root (a bare <label>, or an <a href> for a link variant) ignores width/height/min-width/min-height entirely and renders content-sized whatever you declare. So take both root dimensions from the recorded box in the payload, and give the root a display that honours them \u2014 no score, passing or failing, is evidence they are right. FLUIDITY AFFORDANCE (emit it always): every ROOT width declaration rides width: var(--tendril-root-width, <recorded>px) with that pose's recorded width as the fallback \u2014 per-variant root widths reuse the SAME property name with their own recorded fallbacks. With the property unset this is byte-equivalent truth: identical pixels, identical operability measurement (measured: flipping roots to a bare 100% held 20/20 pixels but made a commit check unmeasurable \u2014 the recorded pin carries real signal). A consumer makes an instance fluid by setting --tendril-root-width (e.g. 100%) on a wrapper, no edit to certified CSS. Widths only: recorded heights and internal geometry stay literal \u2014 fixed heights are often genuine design intent.`;
9866
- const mappedTokens = /* @__PURE__ */ new Set([...forcedStates, ...props.filter((p) => p.kind === "boolean").map((p) => p.name)]);
10320
+ const CHECKED_STATE_TOKENS = /* @__PURE__ */ new Set(["hover", "focus-visible"]);
10321
+ const exercisedBooleans = props.filter((p) => p.kind === "boolean" && configs.some((c) => c.props[p.name] === true)).map((p) => p.name);
10322
+ const checkedTokens = /* @__PURE__ */ new Set([...forcedStates.filter((t) => CHECKED_STATE_TOKENS.has(t)), ...exercisedBooleans]);
9867
10323
  const unmappedInteractionEvidence = interactionEvidence.filter((e) => {
9868
10324
  const sel = e.endsWith(" (selection axis)");
9869
10325
  const key = sel ? kebab3(e.slice(0, -" (selection axis)".length)) : kebab3(e.split("=").slice(1).join("="));
9870
- return !mappedTokens.has(key);
10326
+ return !checkedTokens.has(key);
9871
10327
  });
9872
- return { component: componentIdent, entry, props, forcedStates, interactionEvidence, unmappedInteractionEvidence, syntheticCombos, configs, apiPin, systemApi };
10328
+ return { component: componentIdent, entry, props, forcedStates, interactionEvidence, unmappedInteractionEvidence, satisfiabilityDemoted: [], syntheticCombos, textEntry, archetype, derivedPoseProps, derivedStateTokens: derivedStates, configs, apiPin, systemApi };
9873
10329
  }
9874
10330
  function authorBehaviors(api, extras = {}) {
9875
10331
  const behaviors = [];
@@ -9878,43 +10334,141 @@ function authorBehaviors(api, extras = {}) {
9878
10334
  if (anchor === void 0) return { behaviors, prelude: { controls: [], textInputs: [] }, disclosures };
9879
10335
  const selects = api.props.find((p) => p.kind === "boolean" && /^(selected|checked|isselected|ischecked)$/i.test(p.name));
9880
10336
  const committed = selects === void 0 ? void 0 : api.configs.find((c) => c.props[selects.name] === true && Object.keys(c.props).length === 1);
10337
+ const NATIVE_CONTROL = api.archetype.nativeControl !== "" ? api.archetype.nativeControl : ":is(input, button, select)";
10338
+ const recordedGapBelowFloor = (repA, repB) => {
10339
+ if (repB === void 0 || repB === repA) return void 0;
10340
+ const gap = extras.poseGap?.(repA, repB);
10341
+ return gap !== void 0 && gap < POSE_GAP_FLOOR ? gap : void 0;
10342
+ };
10343
+ const recordedIdentical = (repA, repB) => repB !== void 0 && repB !== repA && extras.posesIdentical?.(repA, repB) === true;
10344
+ const forcedPoseRep = (token) => api.configs.find((c) => {
10345
+ const keys = Object.keys(c.props);
10346
+ return keys.length === 1 && c.props["data-tendril-state"] === token;
10347
+ })?.rep;
10348
+ const demoteEvidence = (matches) => {
10349
+ for (const e of api.interactionEvidence) {
10350
+ if (!matches(e)) continue;
10351
+ if (!api.unmappedInteractionEvidence.includes(e)) api.unmappedInteractionEvidence.push(e);
10352
+ if (!api.satisfiabilityDemoted.includes(e)) api.satisfiabilityDemoted.push(e);
10353
+ }
10354
+ };
10355
+ const demoteTokenToPixelOnly = (token) => {
10356
+ demoteEvidence((e) => kebab3(e.split("=").slice(1).join("=")) === token);
10357
+ };
9881
10358
  if (api.forcedStates.includes("hover")) {
9882
- behaviors.push({ id: "real-hover-responds", config: anchor.rep, steps: [{ hoverChangesPixels: "> *" }] });
10359
+ if (!recordedIdentical(anchor.rep, forcedPoseRep("hover"))) {
10360
+ behaviors.push({ id: "real-hover-responds", config: anchor.rep, steps: [{ hoverChangesPixels: "> *" }] });
10361
+ } else {
10362
+ demoteTokenToPixelOnly("hover");
10363
+ disclosures.push(
10364
+ `real-hover-responds NOT authored: the recorded hover pose is PIXEL-IDENTICAL to rest \u2014 the recording paints no hover difference at all, and demanding a real-hover pixel change would require invented ink; hover stays forceable and pixel-verified only. If hover should respond, the design needs a distinct hover treatment; re-record and re-author`
10365
+ );
10366
+ }
9883
10367
  }
9884
10368
  if (api.forcedStates.includes("focus-visible")) {
9885
- behaviors.push({ id: "focus-ring-visible", config: anchor.rep, steps: [{ tabFocusChangesPixels: true }] });
9886
- behaviors.push({ id: "no-ring-after-mouse-click", config: (committed ?? anchor).rep, steps: [{ mouseClickNoPersistentRing: "> *" }] });
10369
+ if (!recordedIdentical(anchor.rep, forcedPoseRep("focus-visible"))) {
10370
+ behaviors.push({ id: "focus-ring-visible", config: anchor.rep, steps: [{ tabFocusChangesPixels: true }] });
10371
+ } else {
10372
+ demoteTokenToPixelOnly("focus-visible");
10373
+ disclosures.push(
10374
+ `focus-ring-visible NOT authored: the recorded focus pose is PIXEL-IDENTICAL to rest \u2014 the recording paints no focus difference at all, and an invented ring is unrecorded pixels (the prelude keeps the browser's default indicator instead). If keyboard focus should be visible, the design needs a distinct focus treatment; re-record and re-author`
10375
+ );
10376
+ }
10377
+ if (selects === void 0) {
10378
+ behaviors.push({ id: "no-ring-after-mouse-click", config: anchor.rep, steps: [{ mouseClickNoPersistentRing: "> *" }] });
10379
+ } else if (committed !== void 0) {
10380
+ behaviors.push({ id: "no-ring-after-mouse-click", config: committed.rep, steps: [{ mouseClickNoPersistentRing: "> *" }] });
10381
+ } else {
10382
+ disclosures.push(
10383
+ `no-ring-after-mouse-click NOT authored: this selection-bearing component records no already-committed pose to aim the check at, and clicking any other pose legitimately commits a repaint \u2014 the check would fail honest components and pass only statues (it must target a pose where clicking commits nothing)`
10384
+ );
10385
+ }
9887
10386
  }
9888
10387
  const disabledCfg = api.configs.find((c) => c.props["disabled"] === true);
9889
10388
  if (disabledCfg !== void 0) {
9890
- behaviors.push({ id: "disabled-not-focusable", config: disabledCfg.rep, steps: [{ assertNotFocusable: selects === void 0 ? "> *" : "input" }] });
10389
+ behaviors.push({ id: "disabled-not-focusable", config: disabledCfg.rep, steps: [{ assertNotFocusable: selects === void 0 ? "> *" : NATIVE_CONTROL }] });
9891
10390
  }
9892
10391
  const loadingCfg = api.configs.find((c) => c.props["loading"] === true);
9893
10392
  if (loadingCfg !== void 0) {
9894
- behaviors.push({ id: "spinner-animates", config: loadingCfg.rep, steps: [{ assertAnimated: true }] });
10393
+ demoteTokenToPixelOnly("loading");
10394
+ disclosures.push(
10395
+ `spinner-animates NOT authored: the recorded loading pose is a still, and recorded motion truth (prototype transitions) cannot prove an in-pose loop \u2014 demanding a running spinner would invent motion the recording never states; the loading pose stays pixel-verified only (motion ships PRESCRIBED, NOT VERIFIED per ADR-016)`
10396
+ );
9895
10397
  behaviors.push({ id: "reduced-motion-respected", config: loadingCfg.rep, steps: [{ reducedMotionNoAnimation: true }] });
9896
10398
  }
9897
10399
  const selectionProp = selects;
9898
10400
  const committedPose = committed;
9899
10401
  if (selectionProp !== void 0) {
9900
- behaviors.push({ id: "native-control-present", config: anchor.rep, steps: [{ assertVisible: "input" }, { assertFocusable: "input" }] });
10402
+ behaviors.push({ id: "native-control-present", config: anchor.rep, steps: [{ assertFocusable: NATIVE_CONTROL }] });
9901
10403
  behaviors.push({ id: "control-has-pointer-cursor", config: anchor.rep, steps: [{ assertCursor: ["> *", "pointer"] }] });
9902
10404
  if (committedPose !== void 0) {
9903
- behaviors.push({
9904
- id: "activation-commits-recorded-state",
9905
- config: anchor.rep,
9906
- steps: [{ commitMatchesPose: { activate: "input", pose: committedPose.rep } }]
9907
- });
10405
+ const flat = recordedGapBelowFloor(anchor.rep, committedPose.rep);
10406
+ if (flat === void 0) {
10407
+ behaviors.push({
10408
+ id: "activation-commits-recorded-state",
10409
+ config: anchor.rep,
10410
+ steps: [{ commitMatchesPose: { activate: NATIVE_CONTROL, pose: committedPose.rep } }]
10411
+ });
10412
+ } else {
10413
+ demoteEvidence((e) => e.endsWith(" (selection axis)") || kebab3(e.split("=").slice(1).join("=")) === kebab3(selectionProp.name));
10414
+ disclosures.push(
10415
+ `activation-commits-recorded-state NOT authored: the recorded committed pose ("${committedPose.rep}") is pixel-indistinguishable from rest in the recording itself (gap ${flat.toFixed(4)}, floor ${POSE_GAP_FLOOR}) \u2014 committing can never be observed through pixels, on any round, from any component code; selection is presence-checked only. If selection should be visible, the design needs a distinct selected treatment; re-record and re-author`
10416
+ );
10417
+ }
9908
10418
  }
9909
10419
  }
9910
10420
  if (api.props.some((p) => p.kind === "callback" && p.name === "onDismiss")) {
9911
10421
  behaviors.push({
9912
10422
  id: "dismiss-notifies-and-commits",
9913
- config: anchor.rep,
10423
+ // The pose whose recording SHOWS the affordance, when layer
10424
+ // evidence named one — the anchor may not even contain the
10425
+ // layer (run 22's over-constraint).
10426
+ config: extras.dismissTargetRep ?? anchor.rep,
9914
10427
  spyProps: ["onDismiss"],
9915
10428
  steps: [{ assertVisible: "button" }, { assertFocusable: "button" }, { dismissNotifies: { activate: "button", prop: "onDismiss" } }]
9916
10429
  });
9917
10430
  }
10431
+ if (api.archetype.archetype !== "static") {
10432
+ const control = api.archetype.nativeControl;
10433
+ behaviors.push({ id: "ref-reaches-control", config: anchor.rep, steps: [{ assertRefReachesControl: control }] });
10434
+ behaviors.push({
10435
+ id: "rest-props-reach-control",
10436
+ config: anchor.rep,
10437
+ // Minted per run by the checker — a fixed value is hardcodable
10438
+ // with zero collateral (review).
10439
+ props: { "data-tendril-probe": "__TENDRIL_MINT__" },
10440
+ steps: [{ assertAttr: [control, "data-tendril-probe", "__TENDRIL_MINT__"] }]
10441
+ });
10442
+ }
10443
+ const errorProp = api.props.find((p) => p.role === "error");
10444
+ if (errorProp !== void 0 && api.archetype.archetype !== "static") {
10445
+ const errCfg = api.configs.find((c) => typeof c.props[errorProp.name] === "string");
10446
+ if (errCfg !== void 0) {
10447
+ behaviors.push({
10448
+ id: "error-pose-wires-aria",
10449
+ config: errCfg.rep,
10450
+ steps: [
10451
+ { assertAttr: [api.archetype.nativeControl, "aria-invalid", "true"] },
10452
+ // The idref must RESOLVE to an element carrying the message
10453
+ // (review: a dangling aria-describedby is present-not-wired,
10454
+ // the exact distinction the doctrine draws).
10455
+ { assertDescribedByResolves: [api.archetype.nativeControl, String(errCfg.props[errorProp.name])] }
10456
+ ]
10457
+ });
10458
+ }
10459
+ }
10460
+ if (api.archetype.archetype === "text-input") {
10461
+ const control = api.archetype.nativeControl;
10462
+ behaviors.push({ id: "value-entry-works", config: anchor.rep, props: { defaultValue: "" }, steps: [{ typeChangesPixels: [control, "ab"] }, { assertValue: [control, "ab"] }] });
10463
+ behaviors.push({ id: "default-value-reaches-control", config: anchor.rep, props: { defaultValue: "__TENDRIL_MINT__" }, steps: [{ assertValue: [control, "__TENDRIL_MINT__"] }] });
10464
+ behaviors.push({
10465
+ id: "caller-onchange-fires",
10466
+ config: anchor.rep,
10467
+ props: { defaultValue: "" },
10468
+ spyProps: ["onChange"],
10469
+ steps: [{ type: [control, "a"] }, { assertSpyFired: "onChange" }]
10470
+ });
10471
+ }
9918
10472
  for (const sentinel of extras.sentinels ?? []) {
9919
10473
  const marker = `TENDRIL SENTINEL ${sentinel.prop}`;
9920
10474
  behaviors.push({
@@ -9932,31 +10486,114 @@ function authorBehaviors(api, extras = {}) {
9932
10486
  }
9933
10487
  if (api.unmappedInteractionEvidence.length > 0) {
9934
10488
  disclosures.push(
9935
- `interaction-looking poses PIXEL-VERIFIED ONLY (no operability check runs on them \u2014 the authoring vocabulary could not map them): ${api.unmappedInteractionEvidence.join(", ")}`
10489
+ `interaction-looking poses PIXEL-VERIFIED ONLY (no operability check covers them): ${api.unmappedInteractionEvidence.join(", ")}`
9936
10490
  );
9937
10491
  }
9938
- return { behaviors, prelude: { controls: interactive ? ["> *"] : [], textInputs: [] }, disclosures };
10492
+ const textEntry = api.textEntry.length > 0;
10493
+ if (textEntry) {
10494
+ disclosures.push(
10495
+ `text-entry archetype (recorded evidence: ${api.textEntry.join(", ")}) \u2014 the root is exempt from the prelude's user-select:none requirement (selection suppression inherits into the control the canon requires to stay selectable); the editable control is checked selectable, and the root keeps its touch-action check`
10496
+ );
10497
+ }
10498
+ return {
10499
+ behaviors,
10500
+ prelude: {
10501
+ controls: interactive && !textEntry ? ["> *"] : [],
10502
+ textInputs: textEntry ? [":is(input, textarea)"] : [],
10503
+ ...interactive && textEntry ? { touchTargets: ["> *"] } : {}
10504
+ },
10505
+ disclosures
10506
+ };
9939
10507
  }
9940
10508
  function envelopeText(file) {
9941
- return envelopeFirstTextPart(JSON.parse(readFileSync20(file, "utf8")));
10509
+ return envelopeFirstTextPart(JSON.parse(readFileSync21(file, "utf8")));
9942
10510
  }
9943
10511
  function metadataText(file) {
9944
- return envelopeTextContent(JSON.parse(readFileSync20(file, "utf8")));
10512
+ return envelopeTextContent(JSON.parse(readFileSync21(file, "utf8")));
9945
10513
  }
9946
10514
  function dismissEvidence(setDir, repSlugs) {
9947
10515
  for (const slug of repSlugs) {
9948
- const f = path30.join(setDir, slug, "get_design_context.json");
9949
- if (!existsSync24(f)) continue;
10516
+ const f = path31.join(setDir, slug, "get_design_context.json");
10517
+ if (!existsSync25(f)) continue;
9950
10518
  const text = envelopeText(f);
9951
- const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*(?:true|false)\b/i.exec(text) ?? /\b(\w*dismiss\w*)\??\s*:\s*boolean\b/i.exec(text);
9952
- if (propHit !== null) return `emission prop "${propHit[1]}"`;
9953
- for (const m of text.matchAll(/data-name="([^"]+)"/g)) {
9954
- const norm2 = m[1].toLowerCase().replace(/[^a-z0-9]/g, "");
9955
- if (DISMISS_NAMES.has(norm2)) return `layer ${JSON.stringify(m[1])}`;
10519
+ const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10520
+ if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
10521
+ const typedHit = /\b(\w*dismiss\w*)\??\s*:\s*boolean\b/i.exec(text);
10522
+ if (typedHit !== null) {
10523
+ return {
10524
+ visibleReps: [],
10525
+ demoted: `the kit's API declares a dismiss capability (typed member "${typedHit[1]}") but no recorded pose shows a default-on dismiss affordance \u2014 no onDismiss prop and no dismiss check were authored. If it should ship, record a pose with the affordance enabled and visible.`
10526
+ };
10527
+ }
10528
+ for (const m of text.matchAll(/<[^>]*?data-name="([^"]+)"[^>]*?>/g)) {
10529
+ const norm2 = normalizedLayerName(m[1]);
10530
+ if (!DISMISS_NAMES.has(norm2)) continue;
10531
+ const rawId = /data-node-id="([^"]+)"/.exec(m[0])?.[1];
10532
+ const head = rawId === void 0 ? void 0 : rawId.startsWith("I") ? rawId.slice(1).split(";")[0] : rawId;
10533
+ const byId = head === void 0 ? void 0 : poseVisibility(setDir, repSlugs, { nodeId: head });
10534
+ const vis = byId !== void 0 && byId.presentIn.length > 0 ? byId : poseVisibility(setDir, repSlugs, { name: m[1] });
10535
+ if (invisibleInEveryPose(vis)) {
10536
+ return {
10537
+ visibleReps: [],
10538
+ demoted: `a dismiss affordance (layer ${JSON.stringify(m[1])}) exists in the design but is HIDDEN in every recorded pose \u2014 nothing verified stands behind it, so no onDismiss prop and no dismiss check were authored. If it should ship, add a variant that SHOWS it and re-record.`
10539
+ };
10540
+ }
10541
+ const glyphIsTheComponent = (() => {
10542
+ const slugToCheck = vis.visibleIn[0];
10543
+ if (slugToCheck === void 0) return false;
10544
+ const metaFile = path31.join(setDir, slugToCheck, "get_metadata.json");
10545
+ try {
10546
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync21(metaFile, "utf8"))));
10547
+ if (root.children.length !== 1) return false;
10548
+ const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10549
+ return contains(root.children[0]);
10550
+ } catch {
10551
+ return false;
10552
+ }
10553
+ })();
10554
+ if (glyphIsTheComponent) {
10555
+ return {
10556
+ visibleReps: [],
10557
+ demoted: `an x-shaped layer (${JSON.stringify(m[1])}) is the component's SOLE content \u2014 it reads as the component's own glyph (an icon button), not a dismiss affordance; no onDismiss was authored (the sweep's icon-button finding: a plain Control was ordered to "dismiss itself").`
10558
+ };
10559
+ }
10560
+ return { evidence: `layer ${JSON.stringify(m[1])}`, visibleReps: vis.visibleIn };
9956
10561
  }
9957
10562
  }
9958
10563
  return void 0;
9959
10564
  }
10565
+ function recordedReferencePng(setDir, slug) {
10566
+ const f = path31.join(setDir, slug, "get_screenshot.json");
10567
+ if (!existsSync25(f)) return void 0;
10568
+ try {
10569
+ const env = JSON.parse(readFileSync21(f, "utf8")).content.find((c) => c.type === "image");
10570
+ return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10571
+ } catch {
10572
+ return void 0;
10573
+ }
10574
+ }
10575
+ function recordedPoseGap(setDir, repA, repB) {
10576
+ const a = recordedReferencePng(setDir, repA);
10577
+ const b = recordedReferencePng(setDir, repB);
10578
+ if (a === void 0 || b === void 0) return void 0;
10579
+ try {
10580
+ return 1 - compareRenderToReference(a, b).similarity;
10581
+ } catch {
10582
+ return void 0;
10583
+ }
10584
+ }
10585
+ function recordedPosesIdentical(setDir, repA, repB) {
10586
+ const a = recordedReferencePng(setDir, repA);
10587
+ const b = recordedReferencePng(setDir, repB);
10588
+ if (a === void 0 || b === void 0) return void 0;
10589
+ try {
10590
+ const pa = PNG3.sync.read(Buffer.from(a));
10591
+ const pb = PNG3.sync.read(Buffer.from(b));
10592
+ return pa.width === pb.width && pa.height === pb.height && Buffer.compare(pa.data, pb.data) === 0;
10593
+ } catch {
10594
+ return void 0;
10595
+ }
10596
+ }
9960
10597
  function recordedFontNeeds(setDir, opts = {}) {
9961
10598
  const byFamily = /* @__PURE__ */ new Map();
9962
10599
  const unpaired = /* @__PURE__ */ new Set();
@@ -9990,13 +10627,13 @@ function recordedFontNeeds(setDir, opts = {}) {
9990
10627
  }
9991
10628
  };
9992
10629
  const manifest = loadManifest(setDir);
9993
- const setDefs = path30.join(setDir, "get_variable_defs.json");
9994
- if (existsSync24(setDefs)) fromDefs(envelopeText(setDefs));
10630
+ const setDefs = path31.join(setDir, "get_variable_defs.json");
10631
+ if (existsSync25(setDefs)) fromDefs(envelopeText(setDefs));
9995
10632
  for (const rep of manifest.reps) {
9996
- const ctx = path30.join(setDir, rep.slug, "get_design_context.json");
9997
- if (existsSync24(ctx)) fromEmission(envelopeText(ctx));
9998
- const defs = path30.join(setDir, rep.slug, "get_variable_defs.json");
9999
- if (existsSync24(defs)) fromDefs(envelopeText(defs));
10633
+ const ctx = path31.join(setDir, rep.slug, "get_design_context.json");
10634
+ if (existsSync25(ctx)) fromEmission(envelopeText(ctx));
10635
+ const defs = path31.join(setDir, rep.slug, "get_variable_defs.json");
10636
+ if (existsSync25(defs)) fromDefs(envelopeText(defs));
10000
10637
  }
10001
10638
  return [...byFamily.entries()].map(([family, paired]) => {
10002
10639
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10007,10 +10644,10 @@ function symbolFontGlyphCount(setDir, reps) {
10007
10644
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10008
10645
  const glyphs = /* @__PURE__ */ new Set();
10009
10646
  for (const rep of reps) {
10010
- const file = path30.join(setDir, rep, "get_metadata.json");
10011
- if (!existsSync24(file)) continue;
10647
+ const file = path31.join(setDir, rep, "get_metadata.json");
10648
+ if (!existsSync25(file)) continue;
10012
10649
  try {
10013
- const text = JSON.parse(readFileSync20(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10650
+ const text = JSON.parse(readFileSync21(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10014
10651
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10015
10652
  const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
10016
10653
  if (PUA.test(name)) glyphs.add(name);
@@ -10036,8 +10673,8 @@ function recordedTextSlots(setDir, repSlugs) {
10036
10673
  const propRep = [];
10037
10674
  const perRep = [];
10038
10675
  for (const slug of repSlugs) {
10039
- const f = path30.join(setDir, slug, "get_design_context.json");
10040
- if (!existsSync24(f)) continue;
10676
+ const f = path31.join(setDir, slug, "get_design_context.json");
10677
+ if (!existsSync25(f)) continue;
10041
10678
  const code = envelopeText(f);
10042
10679
  const props = /* @__PURE__ */ new Map();
10043
10680
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10061,15 +10698,21 @@ function recordedTextSlots(setDir, repSlugs) {
10061
10698
  perRep.push({ slug, texts, code });
10062
10699
  }
10063
10700
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10701
+ const valuesByAxis = /* @__PURE__ */ new Map();
10064
10702
  for (const slug of repSlugs) {
10065
- const metaFile = path30.join(setDir, slug, "get_metadata.json");
10066
- if (!existsSync24(metaFile)) continue;
10703
+ const metaFile = path31.join(setDir, slug, "get_metadata.json");
10704
+ if (!existsSync25(metaFile)) continue;
10067
10705
  const name = symbolName(metadataText(metaFile));
10068
10706
  if (name === void 0) continue;
10069
10707
  const values = /* @__PURE__ */ new Set();
10070
10708
  for (const part of name.split(",")) {
10071
10709
  const eq = part.indexOf("=");
10072
- if (eq >= 0) values.add(kebab3(part.slice(eq + 1).trim()));
10710
+ if (eq >= 0) {
10711
+ const axis = kebab3(part.slice(0, eq).trim());
10712
+ const value = kebab3(part.slice(eq + 1).trim());
10713
+ (valuesByAxis.get(axis) ?? valuesByAxis.set(axis, /* @__PURE__ */ new Set()).get(axis)).add(value);
10714
+ values.add(value);
10715
+ }
10073
10716
  }
10074
10717
  axisValuesBySlug.set(slug, values);
10075
10718
  }
@@ -10083,7 +10726,11 @@ function recordedTextSlots(setDir, repSlugs) {
10083
10726
  }
10084
10727
  return seen > 0;
10085
10728
  };
10086
- const propNames = [...new Set(propRep.flatMap((r) => [...r.props.keys()]))].filter((n) => !isAxisMirror(n));
10729
+ const alnum = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "");
10730
+ const isAxisNamed = (name) => [...valuesByAxis.entries()].some(
10731
+ ([axis, values]) => alnum(axis) === alnum(name) && ![...values].every((v) => v === "true" || v === "false")
10732
+ );
10733
+ const propNames = [...new Set(propRep.flatMap((r) => [...r.props.keys()]))].filter((n) => !isAxisMirror(n) && !isAxisNamed(n));
10087
10734
  if (propNames.length > 0) {
10088
10735
  return propNames.map((name) => {
10089
10736
  const counts = /* @__PURE__ */ new Map();
@@ -10092,18 +10739,24 @@ function recordedTextSlots(setDir, repSlugs) {
10092
10739
  if (v !== void 0) counts.set(v, (counts.get(v) ?? 0) + 1);
10093
10740
  }
10094
10741
  const def = [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
10742
+ const visibleIn = [];
10743
+ const richIn = [];
10744
+ for (const r of perRep) {
10745
+ const v = propRep.find((pr) => pr.slug === r.slug)?.props.get(name) ?? def;
10746
+ const norm2 = (s) => s.replace(/\s+/g, " ").trim();
10747
+ const singleRun = r.texts.some((t) => norm2(t).includes(norm2(v)));
10748
+ const assembled = !singleRun && norm2(r.texts.join(" ")).includes(norm2(v));
10749
+ if (singleRun || assembled) visibleIn.push(r.slug);
10750
+ if (assembled) richIn.push(r.slug);
10751
+ }
10095
10752
  const overrides = {};
10096
10753
  for (const r of propRep) {
10097
10754
  const v = r.props.get(name);
10098
- if (v !== void 0 && v !== def) overrides[r.slug] = v;
10755
+ if (v !== void 0 && v !== def && !richIn.includes(r.slug)) overrides[r.slug] = v;
10099
10756
  }
10100
- const visibleIn = perRep.filter((r) => {
10101
- const v = propRep.find((pr) => pr.slug === r.slug)?.props.get(name) ?? def;
10102
- return r.texts.some((t) => t.includes(v)) || r.texts.join(" ").replace(/\s+/g, " ").includes(v.replace(/\s+/g, " ").trim());
10103
- }).map((r) => r.slug);
10104
10757
  const refPattern = new RegExp(`\\{\\s*${name}\\s*\\}`);
10105
10758
  const referencedIn = perRep.filter((r) => refPattern.test(r.code)).map((r) => r.slug);
10106
- return { prop: name, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn, referencedIn };
10759
+ return { prop: name, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn, referencedIn, richIn };
10107
10760
  });
10108
10761
  }
10109
10762
  if (perRep.length === 0) return [];
@@ -10150,7 +10803,7 @@ function recordedTextSlots(setDir, repSlugs) {
10150
10803
  usedNames.add(prop);
10151
10804
  const overrides = {};
10152
10805
  for (const [slug, v] of sl.values) if (v !== def) overrides[slug] = v;
10153
- return { prop, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn: [...sl.values.keys()], referencedIn: [] };
10806
+ return { prop, default: def, overrides, varies: Object.keys(overrides).length > 0, visibleIn: [...sl.values.keys()], referencedIn: [], richIn: [] };
10154
10807
  });
10155
10808
  }
10156
10809
  function authorTaskFromSet(setDir, opts = {}) {
@@ -10162,8 +10815,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10162
10815
  const poses = [];
10163
10816
  const missing = [];
10164
10817
  for (const rep of manifest.reps) {
10165
- const metaFile = path30.join(setDir, rep.slug, "get_metadata.json");
10166
- if (!existsSync24(metaFile)) {
10818
+ const metaFile = path31.join(setDir, rep.slug, "get_metadata.json");
10819
+ if (!existsSync25(metaFile)) {
10167
10820
  missing.push(rep.slug);
10168
10821
  continue;
10169
10822
  }
@@ -10177,8 +10830,8 @@ function authorTaskFromSet(setDir, opts = {}) {
10177
10830
  if (missing.length > 0) {
10178
10831
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
10179
10832
  }
10180
- const setMeta = path30.join(setDir, "get_metadata.json");
10181
- const latticeNames = manifest.latticeNames ?? (existsSync24(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10833
+ const setMeta = path31.join(setDir, "get_metadata.json");
10834
+ const latticeNames = manifest.latticeNames ?? (existsSync25(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
10182
10835
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
10183
10836
  const recordedFonts = recordedFontFamilies(setDir);
10184
10837
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -10200,7 +10853,7 @@ function authorTaskFromSet(setDir, opts = {}) {
10200
10853
  ...recordedFonts.length > 0 ? { recordedFonts } : {},
10201
10854
  ...Object.keys(defaults).length > 0 ? { defaults } : {},
10202
10855
  ...textSlots.length > 0 ? { textSlots } : {},
10203
- ...dismissName !== void 0 ? { dismissible: dismissName } : {}
10856
+ ...dismissName?.evidence !== void 0 ? { dismissible: dismissName.evidence } : {}
10204
10857
  });
10205
10858
  const anchorSlug = api.configs.find((c) => Object.keys(c.props).length === 0)?.rep ?? api.configs[0]?.rep;
10206
10859
  const sentinels = textSlots.filter((slot) => !slot.varies).map((slot) => {
@@ -10224,19 +10877,35 @@ function authorTaskFromSet(setDir, opts = {}) {
10224
10877
  }
10225
10878
  return void 0;
10226
10879
  }).filter((x) => x !== void 0);
10227
- const { behaviors, prelude, disclosures } = authorBehaviors(api, { ...sentinels.length > 0 ? { sentinels } : {} });
10880
+ const { behaviors, prelude, disclosures } = authorBehaviors(api, {
10881
+ ...sentinels.length > 0 ? { sentinels } : {},
10882
+ ...dismissName !== void 0 && dismissName.visibleReps.length > 0 ? { dismissTargetRep: dismissName.visibleReps[0] } : {},
10883
+ // Author-time satisfiability inputs (run-22 cycle, Cycle C): the
10884
+ // recorded-PNG oracles — every prescribed pose-distance check is
10885
+ // gated on the recording proving it passable, each gate by the
10886
+ // measure its runtime check actually applies.
10887
+ poseGap: (a, b) => recordedPoseGap(setDir, a, b),
10888
+ posesIdentical: (a, b) => recordedPosesIdentical(setDir, a, b)
10889
+ });
10890
+ if (dismissName?.demoted !== void 0) disclosures.push(dismissName.demoted);
10228
10891
  const puaGlyphs = symbolFontGlyphCount(setDir, manifest.reps.map((r) => r.slug));
10229
10892
  if (puaGlyphs > 0) {
10230
10893
  disclosures.push(
10231
10894
  `SYMBOL-FONT ICONS: ${puaGlyphs} distinct recorded icon glyph(s) come from a symbol font (private-use codepoints) \u2014 the recording carries NO vector geometry for them. Reconstruct each mark against the reference pixels with extreme care, compare magnified crops after every score (run 11 shipped a checkmark where the reference was a dot, and a diamond where it was two chevrons \u2014 both scored above bar), and NEVER give two configs sharing the same recorded glyph different shapes`
10232
10895
  );
10233
10896
  }
10234
- if (dismissName !== void 0) {
10235
- disclosures.push(`dismiss affordance detected from the recording (${dismissName}) \u2014 onDismiss authored; dismiss-notifies-and-commits enforces the notification contract`);
10897
+ if (dismissName?.evidence !== void 0) {
10898
+ disclosures.push(`dismiss affordance detected from the recording (${dismissName.evidence}) \u2014 onDismiss authored; dismiss-notifies-and-commits enforces the notification contract`);
10236
10899
  }
10237
10900
  for (const combo of api.syntheticCombos) {
10238
10901
  disclosures.push(`API split created a reachable pose with NO recorded truth: ${combo} (the design's exclusive axis cannot express it) \u2014 composed behavior only, disclosed to consumers`);
10239
10902
  }
10903
+ for (const slot of textSlots) {
10904
+ if (slot.richIn.length === 0) continue;
10905
+ disclosures.push(
10906
+ `content prop (recorded ${JSON.stringify(slot.default)}) renders as RICH multi-run text in pose(s) ${slot.richIn.join(", ")} \u2014 those poses never receive the prop through configs (the prescribed contract renders caller strings plainly, which would contradict the recorded formatting on pixels); each renders its recorded rich markup prop-absent and stays pixel-verified that way`
10907
+ );
10908
+ }
10240
10909
  for (const slot of textSlots) {
10241
10910
  if (slot.varies) continue;
10242
10911
  const sentineled = sentinels.some((sn) => api.props.some((pr) => pr.name === sn.prop && pr.default === slot.default));
@@ -10304,12 +10973,13 @@ ${PRELUDE_CONTRACT}
10304
10973
  ${opts.colorScheme === void 0 ? "" : `
10305
10974
  RESOLVED FOR THIS RECORDING: it is ${opts.colorScheme}-mode truth, so pin color-scheme: ${opts.colorScheme} on the root. A conditional rule you have to resolve yourself is a rule you will get wrong \u2014 this is the answer, not the question.`}`;
10306
10975
  }
10307
- var PoseCompletenessError, kebab3, camel, pascal, RESERVED_PROPS, axisPropName, isStateAxis, DISMISS_NAMES, symbolName, STYLE_WEIGHTS2, styleWeight;
10976
+ var PoseCompletenessError, kebab3, camel, pascal, RESERVED_PROPS, NATIVE_ATTR_COLLISIONS, axisPropName, isStateAxis, DISMISS_NAMES, symbolName, STYLE_WEIGHTS2, styleWeight;
10308
10977
  var init_brief = __esm({
10309
10978
  "packages/generate/src/brief.ts"() {
10310
10979
  "use strict";
10311
10980
  init_src();
10312
10981
  init_src5();
10982
+ init_archetype();
10313
10983
  PoseCompletenessError = class extends Error {
10314
10984
  inexpressible;
10315
10985
  constructor(inexpressible) {
@@ -10328,9 +10998,10 @@ var init_brief = __esm({
10328
10998
  return c === "" ? c : c[0].toUpperCase() + c.slice(1);
10329
10999
  };
10330
11000
  RESERVED_PROPS = /* @__PURE__ */ new Set(["style", "classname", "children", "key", "ref", "id"]);
11001
+ NATIVE_ATTR_COLLISIONS = /* @__PURE__ */ new Set(["size", "type", "checked", "multiple", "value"]);
10331
11002
  axisPropName = (component, axis) => {
10332
11003
  const name = camel(axis);
10333
- return RESERVED_PROPS.has(name.toLowerCase()) ? camel(`${component} ${axis}`) : name;
11004
+ return RESERVED_PROPS.has(name.toLowerCase()) || NATIVE_ATTR_COLLISIONS.has(name.toLowerCase()) ? camel(`${component} ${axis}`) : name;
10334
11005
  };
10335
11006
  isStateAxis = (axis) => kebab3(axis) === "state";
10336
11007
  DISMISS_NAMES = /* @__PURE__ */ new Set(["x", "close", "dismiss", "closebutton", "dismissbutton", "xbutton", "iconx", "iconclose", "icondismiss"]);
@@ -10361,10 +11032,10 @@ var init_brief = __esm({
10361
11032
  });
10362
11033
 
10363
11034
  // packages/generate/src/segments.ts
10364
- import { existsSync as existsSync25, readFileSync as readFileSync21, readdirSync as readdirSync9 } from "node:fs";
10365
- import path31 from "node:path";
11035
+ import { existsSync as existsSync26, readFileSync as readFileSync22, readdirSync as readdirSync9 } from "node:fs";
11036
+ import path32 from "node:path";
10366
11037
  function repText(set, rep, tool) {
10367
- const env = JSON.parse(readFileSync21(path31.join(set, rep, `${tool}.json`), "utf8"));
11038
+ const env = JSON.parse(readFileSync22(path32.join(set, rep, `${tool}.json`), "utf8"));
10368
11039
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
10369
11040
  }
10370
11041
  function stripFigmaInstructions(emission) {
@@ -10424,20 +11095,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
10424
11095
  }
10425
11096
  function buildSegments(task, mode = "fenced") {
10426
11097
  const SET = task.set;
10427
- let defsRecorded = existsSync25(path31.join(SET, "get_variable_defs.json"));
11098
+ let defsRecorded = existsSync26(path32.join(SET, "get_variable_defs.json"));
10428
11099
  let rawDefs = {};
10429
- if (existsSync25(path31.join(SET, "get_variable_defs.json"))) {
10430
- const text = envelopeFirstTextPart(JSON.parse(readFileSync21(path31.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11100
+ if (existsSync26(path32.join(SET, "get_variable_defs.json"))) {
11101
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync22(path32.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
10431
11102
  try {
10432
11103
  rawDefs = JSON.parse(text);
10433
11104
  } catch {
10434
11105
  }
10435
11106
  } else {
10436
11107
  for (const cfg of task.configs) {
10437
- const f = path31.join(SET, cfg.rep, "get_variable_defs.json");
10438
- if (!existsSync25(f)) continue;
11108
+ const f = path32.join(SET, cfg.rep, "get_variable_defs.json");
11109
+ if (!existsSync26(f)) continue;
10439
11110
  defsRecorded = true;
10440
- const text = envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) || "{}";
11111
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync22(f, "utf8"))) || "{}";
10441
11112
  try {
10442
11113
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
10443
11114
  } catch {
@@ -10445,8 +11116,8 @@ function buildSegments(task, mode = "fenced") {
10445
11116
  }
10446
11117
  }
10447
11118
  const emissionTexts = task.configs.map((cfg) => {
10448
- const f = path31.join(SET, cfg.rep, "get_design_context.json");
10449
- return existsSync25(f) ? envelopeFirstTextPart(JSON.parse(readFileSync21(f, "utf8"))) : "";
11119
+ const f = path32.join(SET, cfg.rep, "get_design_context.json");
11120
+ return existsSync26(f) ? envelopeFirstTextPart(JSON.parse(readFileSync22(f, "utf8"))) : "";
10450
11121
  });
10451
11122
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
10452
11123
  const defs = JSON.stringify(map, null, 1);
@@ -10463,9 +11134,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
10463
11134
  for (const cfg of task.configs) {
10464
11135
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
10465
11136
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
10466
- const assets = readdirSync9(path31.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11137
+ const assets = readdirSync9(path32.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
10467
11138
  \`\`\`svg
10468
- ${readFileSync21(path31.join(SET, cfg.rep, f), "utf8")}
11139
+ ${readFileSync22(path32.join(SET, cfg.rep, f), "utf8")}
10469
11140
  \`\`\``).join("\n");
10470
11141
  parts.push(`
10471
11142
  ## Config ${cfg.rep} \u2192 ${cfg.component} ${JSON.stringify(cfg.props)}
@@ -10490,7 +11161,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
10490
11161
  } else {
10491
11162
  parts.push(`
10492
11163
  ## Output format
10493
- Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path31.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
11164
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path32.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
10494
11165
  }
10495
11166
  return parts.join("\n");
10496
11167
  }
@@ -10558,8 +11229,8 @@ var init_adapter = __esm({
10558
11229
 
10559
11230
  // packages/generate/src/bundle-emit.ts
10560
11231
  import { createHash as createHash5 } from "node:crypto";
10561
- import { copyFileSync, existsSync as existsSync26, mkdirSync as mkdirSync7, readFileSync as readFileSync22, readdirSync as readdirSync10, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
10562
- import path32 from "node:path";
11232
+ import { copyFileSync, existsSync as existsSync27, mkdirSync as mkdirSync7, readFileSync as readFileSync23, readdirSync as readdirSync10, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
11233
+ import path33 from "node:path";
10563
11234
  function pinFromConfigs(configs) {
10564
11235
  const domains = /* @__PURE__ */ new Map();
10565
11236
  const kinds = /* @__PURE__ */ new Map();
@@ -10628,9 +11299,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10628
11299
  const notices = [];
10629
11300
  const licenseTexts = /* @__PURE__ */ new Map();
10630
11301
  for (const face of faces) {
10631
- const src = path32.join(cacheDir, path32.basename(face.file));
10632
- const target = `./fonts/${path32.basename(face.file)}`;
10633
- const format = FONT_FORMATS[path32.extname(face.file).toLowerCase()] ?? "truetype";
11302
+ const src = path33.join(cacheDir, path33.basename(face.file));
11303
+ const target = `./fonts/${path33.basename(face.file)}`;
11304
+ const format = FONT_FORMATS[path33.extname(face.file).toLowerCase()] ?? "truetype";
10634
11305
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
10635
11306
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
10636
11307
  const license = normalizeFontLicense(face.license);
@@ -10668,14 +11339,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10668
11339
  `/* ${decl} */`
10669
11340
  );
10670
11341
  }
10671
- } else if (existsSync26(src) && createHash5("sha256").update(readFileSync22(src)).digest("hex") === face.sha256) {
10672
- mkdirSync7(path32.join(bundleDir, "fonts"), { recursive: true });
10673
- copyFileSync(src, path32.join(bundleDir, "fonts", path32.basename(face.file)));
11342
+ } else if (existsSync27(src) && createHash5("sha256").update(readFileSync23(src)).digest("hex") === face.sha256) {
11343
+ mkdirSync7(path33.join(bundleDir, "fonts"), { recursive: true });
11344
+ copyFileSync(src, path33.join(bundleDir, "fonts", path33.basename(face.file)));
10674
11345
  licenseTexts.set(terms.file, terms.text);
10675
11346
  const upstream = upstreamAttribution(face);
10676
11347
  notices.push(
10677
11348
  "",
10678
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path32.basename(face.file)}`,
11349
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path33.basename(face.file)}`,
10679
11350
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
10680
11351
  ` source: ${face.source}`,
10681
11352
  ` sha256: ${face.sha256}`,
@@ -10689,9 +11360,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10689
11360
  }
10690
11361
  if (lines.length === 0) return null;
10691
11362
  if (notices.length > 0) {
10692
- const fontsDir = path32.join(bundleDir, "fonts");
10693
- for (const [file, text] of licenseTexts) writeFileSync11(path32.join(fontsDir, file), text);
10694
- writeFileSync11(path32.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11363
+ const fontsDir = path33.join(bundleDir, "fonts");
11364
+ for (const [file, text] of licenseTexts) writeFileSync11(path33.join(fontsDir, file), text);
11365
+ writeFileSync11(path33.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
10695
11366
  `);
10696
11367
  header.push(
10697
11368
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -10703,10 +11374,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
10703
11374
  `;
10704
11375
  }
10705
11376
  function countLatticeSymbols(setDir) {
10706
- const manifestFile = path32.join(setDir, "recording-set.json");
10707
- if (existsSync26(manifestFile)) {
11377
+ const manifestFile = path33.join(setDir, "recording-set.json");
11378
+ if (existsSync27(manifestFile)) {
10708
11379
  try {
10709
- const stored = JSON.parse(readFileSync22(manifestFile, "utf8"));
11380
+ const stored = JSON.parse(readFileSync23(manifestFile, "utf8"));
10710
11381
  if (stored.variantScope !== "component-set") return null;
10711
11382
  const lattice = stored.latticeNames;
10712
11383
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -10714,13 +11385,13 @@ function countLatticeSymbols(setDir) {
10714
11385
  }
10715
11386
  }
10716
11387
  const files = [
10717
- path32.join(setDir, "get_metadata.json"),
10718
- ...existsSync26(setDir) ? readdirSync10(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path32.join(setDir, f)) : []
10719
- ].filter((f) => existsSync26(f));
11388
+ path33.join(setDir, "get_metadata.json"),
11389
+ ...existsSync27(setDir) ? readdirSync10(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path33.join(setDir, f)) : []
11390
+ ].filter((f) => existsSync27(f));
10720
11391
  if (files.length === 0) return null;
10721
11392
  let count = 0;
10722
11393
  for (const f of files) {
10723
- const text = envelopeTextContent(JSON.parse(readFileSync22(f, "utf8")));
11394
+ const text = envelopeTextContent(JSON.parse(readFileSync23(f, "utf8")));
10724
11395
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
10725
11396
  }
10726
11397
  return count > 0 ? count : null;
@@ -10728,21 +11399,21 @@ function countLatticeSymbols(setDir) {
10728
11399
  function recordingSetHash(setDir, configs) {
10729
11400
  const relPaths = [];
10730
11401
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
10731
- if (existsSync26(path32.join(setDir, name))) relPaths.push(name);
11402
+ if (existsSync27(path33.join(setDir, name))) relPaths.push(name);
10732
11403
  }
10733
11404
  for (const cfg of configs) {
10734
11405
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
10735
- if (existsSync26(path32.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11406
+ if (existsSync27(path33.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
10736
11407
  }
10737
- if (existsSync26(path32.join(setDir, cfg.rep))) {
10738
- for (const asset of readdirSync10(path32.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11408
+ if (existsSync27(path33.join(setDir, cfg.rep))) {
11409
+ for (const asset of readdirSync10(path33.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
10739
11410
  relPaths.push(`${cfg.rep}/${asset}`);
10740
11411
  }
10741
11412
  }
10742
11413
  }
10743
11414
  return hashRecordingSet(
10744
11415
  relPaths,
10745
- (p) => new Uint8Array(readFileSync22(path32.join(setDir, p))),
11416
+ (p) => new Uint8Array(readFileSync23(path33.join(setDir, p))),
10746
11417
  (chunks) => {
10747
11418
  const h = createHash5("sha256");
10748
11419
  for (const c of chunks) h.update(c);
@@ -10756,18 +11427,24 @@ function statusOf(s) {
10756
11427
  }
10757
11428
  function emitBundleV1(opts) {
10758
11429
  const substituted = (opts.substitutedFamilies ?? []).length > 0;
11430
+ const parityFailed = new Set(opts.behaviors.filter((b) => b.id.startsWith("parity:") && !b.pass).map((b) => b.id.slice("parity:".length)));
10759
11431
  const statuses = opts.scores.map((s) => {
10760
11432
  const tier = statusOf(s);
10761
- return { rep: s.rep, similarity: s.similarity, inkRecall: s.inkRecall, ...s.exact !== void 0 ? { exact: s.exact } : {}, status: substituted && tier === "certified" ? "pass" : tier };
11433
+ const demoted = parityFailed.has(s.rep) ? "fail" : substituted && tier === "certified" ? "pass" : tier;
11434
+ return { rep: s.rep, similarity: s.similarity, inkRecall: s.inkRecall, ...s.exact !== void 0 ? { exact: s.exact } : {}, status: demoted };
10762
11435
  });
10763
11436
  const certified = statuses.filter((s) => s.status === "certified").length;
10764
11437
  const pass = statuses.filter((s) => s.status !== "fail").length;
10765
11438
  const lattice = countLatticeSymbols(opts.task.set);
10766
- const interaction = opts.behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:"));
11439
+ const interaction = opts.behaviors.filter(
11440
+ (b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:") && !b.id.startsWith("content-prop-renders(") && !CONTRACT_CHECK_IDS.has(b.id)
11441
+ );
11442
+ const content = opts.behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
10767
11443
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
10768
11444
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
10769
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path32.join(opts.bundleDir, f)).filter((f) => existsSync26(f));
10770
- const families = cssFontFamilies(cssFiles.map((f) => readFileSync22(f, "utf8")).join("\n"));
11445
+ const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
11446
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path33.join(opts.bundleDir, f)).filter((f) => existsSync27(f));
11447
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync23(f, "utf8")).join("\n"));
10771
11448
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
10772
11449
  family: f.family,
10773
11450
  weight: f.weight,
@@ -10796,7 +11473,7 @@ function emitBundleV1(opts) {
10796
11473
  // resolvable via verify's --set override).
10797
11474
  path: (() => {
10798
11475
  const base = process.env["INIT_CWD"] ?? process.cwd();
10799
- const rel = path32.relative(base, opts.task.set);
11476
+ const rel = path33.relative(base, opts.task.set);
10800
11477
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
10801
11478
  })(),
10802
11479
  component: opts.componentName,
@@ -10816,25 +11493,29 @@ function emitBundleV1(opts) {
10816
11493
  interactionPassed: interaction.filter((b) => b.pass).length,
10817
11494
  interactionChecks: interaction.length,
10818
11495
  preludePassed: prelude.filter((b) => b.pass).length,
10819
- preludeChecks: prelude.length
11496
+ preludeChecks: prelude.length,
11497
+ contentPassed: content.filter((b) => b.pass).length,
11498
+ contentChecks: content.length,
11499
+ parityPassed: parity.filter((b) => b.pass).length,
11500
+ parityChecks: parity.length
10820
11501
  })
10821
11502
  };
10822
11503
  const written = [];
10823
- const manifestPath2 = path32.join(opts.bundleDir, "component.json");
11504
+ const manifestPath2 = path33.join(opts.bundleDir, "component.json");
10824
11505
  writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
10825
11506
  `);
10826
11507
  written.push(manifestPath2);
10827
- const stylesPath = path32.join(opts.bundleDir, "styles.css");
10828
- if (existsSync26(stylesPath)) {
11508
+ const stylesPath = path33.join(opts.bundleDir, "styles.css");
11509
+ if (existsSync27(stylesPath)) {
10829
11510
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
10830
- const current = readFileSync22(stylesPath, "utf8");
11511
+ const current = readFileSync23(stylesPath, "utf8");
10831
11512
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
10832
11513
  writeFileSync11(stylesPath, `${comment}
10833
11514
  ${stripped}`);
10834
11515
  written.push(stylesPath);
10835
11516
  }
10836
- const fontsCssPath = path32.join(opts.bundleDir, "fonts.css");
10837
- rmSync3(path32.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11517
+ const fontsCssPath = path33.join(opts.bundleDir, "fonts.css");
11518
+ rmSync3(path33.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
10838
11519
  rmSync3(fontsCssPath, { force: true });
10839
11520
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
10840
11521
  if (fontsCss !== null) {
@@ -10842,7 +11523,10 @@ ${stripped}`);
10842
11523
  written.push(fontsCssPath);
10843
11524
  }
10844
11525
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
10845
- const statusLine = `bundle: ${pass}/${statuses.length} recorded configs \u2265 pass bar (${certified} certified), ` + (interaction.length === 0 ? "interaction behaviors NONE VERIFIED" : `interaction behaviors ${interaction.filter((b) => b.pass).length}/${interaction.length}`) + (parity.length === 0 ? "" : `, state parity ${parity.filter((b) => b.pass).length}/${parity.length}`) + (prelude.length === 0 ? "" : `, page hygiene ${prelude.filter((b) => b.pass).length}/${prelude.length}`) + (unrecorded === null ? "" : `; ${unrecorded} lattice configs UNVERIFIED`) + ` \u2014 claims in component.json, recompute with \`tendril verify\``;
11526
+ const statusLine = `bundle: ${pass}/${statuses.length} recorded configs \u2265 pass bar (${certified} certified), ` + (interaction.length === 0 ? "interaction behaviors NONE VERIFIED" : `interaction behaviors ${interaction.filter((b) => b.pass).length}/${interaction.length}`) + (content.length === 0 ? "" : `, content slots ${content.filter((b) => b.pass).length}/${content.length}`) + (contract.length === 0 ? "" : `, component contract ${contract.filter((b) => b.pass).length}/${contract.length}`) + (parity.length === 0 ? "" : `, state parity ${parity.filter((b) => b.pass).length}/${parity.length}`) + (prelude.length === 0 ? "" : `, page hygiene ${prelude.filter((b) => b.pass).length}/${prelude.length}`) + // Silence at null was readable as complete coverage (the sweep's
11527
+ // M5 tail): the tri-state's worst spelling, in generate's one
11528
+ // non-suppressible line.
11529
+ (unrecorded === null ? "; coverage denominator UNKNOWN" : `; ${unrecorded} lattice configs UNVERIFIED`) + ` \u2014 claims in component.json, recompute with \`tendril verify\``;
10846
11530
  return { manifest, statusLine, written };
10847
11531
  }
10848
11532
  var FONT_FORMATS, BARS, NOTICE_PREAMBLE, OFL_1_1_TEXT, UFL_1_0_TEXT, APACHE_2_0_TEXT, REDISTRIBUTABLE;
@@ -11264,8 +11948,8 @@ DEALINGS IN THE FONT SOFTWARE.
11264
11948
 
11265
11949
  // packages/generate/src/compose-pins.ts
11266
11950
  import { createHash as createHash6 } from "node:crypto";
11267
- import { existsSync as existsSync27, readFileSync as readFileSync23, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
11268
- import path33 from "node:path";
11951
+ import { existsSync as existsSync28, readFileSync as readFileSync24, readdirSync as readdirSync11, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
11952
+ import path34 from "node:path";
11269
11953
  function bundleDirs(roots, depth = 4) {
11270
11954
  const found = [];
11271
11955
  const seen = /* @__PURE__ */ new Set();
@@ -11274,11 +11958,11 @@ function bundleDirs(roots, depth = 4) {
11274
11958
  try {
11275
11959
  key = realpathSync3(dir);
11276
11960
  } catch {
11277
- key = path33.resolve(dir);
11961
+ key = path34.resolve(dir);
11278
11962
  }
11279
11963
  if (seen.has(key)) return;
11280
11964
  seen.add(key);
11281
- if (existsSync27(path33.join(dir, "component.json"))) {
11965
+ if (existsSync28(path34.join(dir, "component.json"))) {
11282
11966
  found.push(key);
11283
11967
  return;
11284
11968
  }
@@ -11291,14 +11975,14 @@ function bundleDirs(roots, depth = 4) {
11291
11975
  }
11292
11976
  for (const e of entries) {
11293
11977
  if (e === "node_modules" || e.startsWith(".")) continue;
11294
- const full = path33.join(dir, e);
11978
+ const full = path34.join(dir, e);
11295
11979
  try {
11296
11980
  if (statSync3(full).isDirectory()) walk2(full, remaining - 1);
11297
11981
  } catch {
11298
11982
  }
11299
11983
  }
11300
11984
  };
11301
- for (const r of roots) walk2(path33.resolve(r), depth);
11985
+ for (const r of roots) walk2(path34.resolve(r), depth);
11302
11986
  return found;
11303
11987
  }
11304
11988
  function composedPins(hostSet, libraryRoots) {
@@ -11317,7 +12001,7 @@ function composedPins(hostSet, libraryRoots) {
11317
12001
  let pinned = false;
11318
12002
  const failures = [];
11319
12003
  for (const rel of partnerRels) {
11320
- const partnerSet = path33.resolve(hostSet, rel);
12004
+ const partnerSet = path34.resolve(hostSet, rel);
11321
12005
  let partnerTask;
11322
12006
  let partnerManifest;
11323
12007
  try {
@@ -11346,7 +12030,7 @@ function composedPins(hostSet, libraryRoots) {
11346
12030
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
11347
12031
  const matches = candidates.filter((dir) => {
11348
12032
  try {
11349
- const parsed = readBundleManifest(readFileSync23(path33.join(dir, "component.json"), "utf8"));
12033
+ const parsed = readBundleManifest(readFileSync24(path34.join(dir, "component.json"), "utf8"));
11350
12034
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
11351
12035
  } catch {
11352
12036
  return false;
@@ -11359,13 +12043,13 @@ function composedPins(hostSet, libraryRoots) {
11359
12043
  continue;
11360
12044
  }
11361
12045
  if (matches.length > 1) {
11362
- failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path33.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
12046
+ failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path34.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
11363
12047
  continue;
11364
12048
  }
11365
12049
  const bundleDir = matches[0];
11366
12050
  let manifest;
11367
12051
  try {
11368
- manifest = readBundleManifest(readFileSync23(path33.join(bundleDir, "component.json"), "utf8")).manifest;
12052
+ manifest = readBundleManifest(readFileSync24(path34.join(bundleDir, "component.json"), "utf8")).manifest;
11369
12053
  } catch {
11370
12054
  manifest = void 0;
11371
12055
  }
@@ -11382,8 +12066,8 @@ function composedPins(hostSet, libraryRoots) {
11382
12066
  const moduleFiles = [];
11383
12067
  let fileIssue;
11384
12068
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
11385
- const file = path33.join(bundleDir, name);
11386
- if (!existsSync27(file)) {
12069
+ const file = path34.join(bundleDir, name);
12070
+ if (!existsSync28(file)) {
11387
12071
  if (name === manifest.entry || name === "styles.css") {
11388
12072
  fileIssue = `${rel}: partner bundle is missing ${name}`;
11389
12073
  break;
@@ -11392,7 +12076,7 @@ function composedPins(hostSet, libraryRoots) {
11392
12076
  }
11393
12077
  let bytes;
11394
12078
  try {
11395
- bytes = readFileSync23(file);
12079
+ bytes = readFileSync24(file);
11396
12080
  } catch {
11397
12081
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
11398
12082
  break;
@@ -11463,14 +12147,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
11463
12147
  const checks = [];
11464
12148
  let entrySource = "";
11465
12149
  try {
11466
- entrySource = readFileSync23(path33.join(candidateDir, hostEntry), "utf8");
12150
+ entrySource = readFileSync24(path34.join(candidateDir, hostEntry), "utf8");
11467
12151
  } catch {
11468
12152
  }
11469
- const candidateRoot = path33.resolve(candidateDir);
12153
+ const candidateRoot = path34.resolve(candidateDir);
11470
12154
  for (const pin of pins) {
11471
12155
  const dir = composedModuleDir(pin.partnerName);
11472
- const resolvedDir = path33.resolve(candidateDir, dir);
11473
- if (!resolvedDir.startsWith(candidateRoot + path33.sep)) {
12156
+ const resolvedDir = path34.resolve(candidateDir, dir);
12157
+ if (!resolvedDir.startsWith(candidateRoot + path34.sep)) {
11474
12158
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11475
12159
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
11476
12160
  continue;
@@ -11481,12 +12165,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
11481
12165
  wrong.push(`${f.name} refused (not a plain path segment)`);
11482
12166
  continue;
11483
12167
  }
11484
- const target = path33.join(candidateDir, dir, f.name);
11485
- if (!existsSync27(target)) {
12168
+ const target = path34.join(candidateDir, dir, f.name);
12169
+ if (!existsSync28(target)) {
11486
12170
  wrong.push(`${f.name} missing`);
11487
12171
  continue;
11488
12172
  }
11489
- const sha = createHash6("sha256").update(readFileSync23(target)).digest("hex");
12173
+ const sha = createHash6("sha256").update(readFileSync24(target)).digest("hex");
11490
12174
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
11491
12175
  }
11492
12176
  checks.push({
@@ -11512,15 +12196,15 @@ var init_compose_pins = __esm({
11512
12196
  init_src4();
11513
12197
  init_brief();
11514
12198
  init_bundle_emit();
11515
- composedModuleDir = (partnerName) => path33.posix.join("composed", partnerName);
12199
+ composedModuleDir = (partnerName) => path34.posix.join("composed", partnerName);
11516
12200
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
11517
12201
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
11518
12202
  }
11519
12203
  });
11520
12204
 
11521
12205
  // packages/generate/src/motion.ts
11522
- import { existsSync as existsSync28, readFileSync as readFileSync24, readdirSync as readdirSync12, statSync as statSync4 } from "node:fs";
11523
- import path34 from "node:path";
12206
+ import { existsSync as existsSync29, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync4 } from "node:fs";
12207
+ import path35 from "node:path";
11524
12208
  function springProgress(u, bounce) {
11525
12209
  const decay = Math.log(100);
11526
12210
  if (bounce <= 0) {
@@ -11550,7 +12234,7 @@ function motionCanonCss() {
11550
12234
  ` --tendril-motion-${tier}-ease: ${springLinearEasing(t.bounce)};`
11551
12235
  ]);
11552
12236
  return [
11553
- ":root {",
12237
+ ".\xABcomponent-root-class\xBB { /* substitute YOUR root class \u2014 never :root (token names on :root collide across composed bundles) */",
11554
12238
  ...lines,
11555
12239
  "}",
11556
12240
  "@media (prefers-reduced-motion: reduce) {",
@@ -11567,10 +12251,10 @@ function motionCanonCss() {
11567
12251
  ].join("\n");
11568
12252
  }
11569
12253
  function motionTruthFor(setDir) {
11570
- const file = path34.join(setDir, "get_motion_context.json");
11571
- if (existsSync28(file) && usableEnvelope(file, "get_motion_context").ok) {
12254
+ const file = path35.join(setDir, "get_motion_context.json");
12255
+ if (existsSync29(file) && usableEnvelope(file, "get_motion_context").ok) {
11572
12256
  try {
11573
- const text = envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
12257
+ const text = envelopeTextContent(JSON.parse(readFileSync25(file, "utf8")));
11574
12258
  return text.trim() === "" ? { state: "recorded-empty" } : { state: "recorded", text };
11575
12259
  } catch {
11576
12260
  }
@@ -11582,21 +12266,21 @@ function motionTruthFor(setDir) {
11582
12266
  }
11583
12267
  }
11584
12268
  function motionDisclosure(bundleDir, setDir) {
11585
- const sheets = ["styles.css", "tokens.css"].map((f) => path34.join(bundleDir, f));
11586
- const composedRoot = path34.join(bundleDir, "composed");
12269
+ const sheets = ["styles.css", "tokens.css"].map((f) => path35.join(bundleDir, f));
12270
+ const composedRoot = path35.join(bundleDir, "composed");
11587
12271
  try {
11588
12272
  for (const entry of readdirSync12(composedRoot).sort()) {
11589
- const dir = path34.join(composedRoot, entry);
12273
+ const dir = path35.join(composedRoot, entry);
11590
12274
  try {
11591
12275
  if (!statSync4(dir).isDirectory()) continue;
11592
12276
  } catch {
11593
12277
  continue;
11594
12278
  }
11595
- sheets.push(path34.join(dir, "styles.css"), path34.join(dir, "tokens.css"));
12279
+ sheets.push(path35.join(dir, "styles.css"), path35.join(dir, "tokens.css"));
11596
12280
  }
11597
12281
  } catch {
11598
12282
  }
11599
- const css = sheets.filter((f) => existsSync28(f)).map((f) => readFileSync24(f, "utf8")).join("\n");
12283
+ const css = sheets.filter((f) => existsSync29(f)).map((f) => readFileSync25(f, "utf8")).join("\n");
11600
12284
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
11601
12285
  return {
11602
12286
  present: true,
@@ -11615,7 +12299,7 @@ ${truth.text.length > RECORDED_QUOTE_CAP ? `${truth.text.slice(0, RECORDED_QUOTE
11615
12299
  === MOTION (PRESCRIBED, NOT VERIFIED \u2014 ADR-016) ===
11616
12300
  ${truthBlock}
11617
12301
  DOCTRINE (motion is information, not decoration \u2014 apply where a state change benefits from continuity, never decoratively):
11618
- - Copy these tokens VERBATIM into styles.css when you apply ANY motion; no component invents its own duration \u2014 always the token:
12302
+ - Copy these tokens into styles.css when you apply ANY motion \u2014 declarations VERBATIM, but substitute your component's root class for \xABcomponent-root-class\xBB (never :root: the token-scope rule applies to motion tokens too); no component invents its own duration \u2014 always the token:
11619
12303
  ${motionCanonCss()}
11620
12304
  - Tier by SIZE of what moves: fast = hover/focus/selection/tooltips; moderate = local panels, indicators, switch thumbs; slow = dialogs and large surfaces.
11621
12305
  - ENTERS use the tier's spring ease (var(--tendril-motion-<tier>-ease)) over var(--tendril-motion-<tier>); EXITS are plain quicker tweens: ease-out over var(--tendril-motion-<tier>-exit) \u2014 a dismissal reads as crisp and final, never the entrance replayed in reverse.
@@ -11647,6 +12331,7 @@ var init_motion = __esm({
11647
12331
  var init_src7 = __esm({
11648
12332
  "packages/generate/src/index.ts"() {
11649
12333
  "use strict";
12334
+ init_archetype();
11650
12335
  init_engine();
11651
12336
  init_engine_curated();
11652
12337
  init_loop2();
@@ -11662,9 +12347,9 @@ var init_src7 = __esm({
11662
12347
  });
11663
12348
 
11664
12349
  // packages/cli/src/font-guidance.ts
11665
- import path35 from "node:path";
12350
+ import path36 from "node:path";
11666
12351
  function fontsUnprovenRemediation(setDir) {
11667
- const set = setDir === void 0 ? void 0 : path35.resolve(setDir);
12352
+ const set = setDir === void 0 ? void 0 : path36.resolve(setDir);
11668
12353
  if (set !== void 0) {
11669
12354
  try {
11670
12355
  const needs = recordedFontNeeds(set);
@@ -11739,8 +12424,8 @@ __export(fonts_exports, {
11739
12424
  runFontsResolveSet: () => runFontsResolveSet,
11740
12425
  runFontsStatus: () => runFontsStatus
11741
12426
  });
11742
- import { existsSync as existsSync29, readFileSync as readFileSync25 } from "node:fs";
11743
- import path36 from "node:path";
12427
+ import { existsSync as existsSync30, readFileSync as readFileSync26 } from "node:fs";
12428
+ import path37 from "node:path";
11744
12429
  async function runFontsResolve(opts) {
11745
12430
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
11746
12431
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -11761,7 +12446,7 @@ async function runFontsResolve(opts) {
11761
12446
  }
11762
12447
  }
11763
12448
  async function runFontsResolveSet(opts) {
11764
- const setDir = path36.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
12449
+ const setDir = path37.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
11765
12450
  let needs = [];
11766
12451
  try {
11767
12452
  needs = recordedFontNeeds(setDir);
@@ -11844,16 +12529,16 @@ async function runFontsResolveSet(opts) {
11844
12529
  }
11845
12530
  }
11846
12531
  function runFontsStatus(opts) {
11847
- const manifestPath2 = path36.join(opts.cacheDir, "manifest.json");
11848
- if (!existsSync29(manifestPath2)) {
12532
+ const manifestPath2 = path37.join(opts.cacheDir, "manifest.json");
12533
+ if (!existsSync30(manifestPath2)) {
11849
12534
  fail(opts, ExitCode.FontsUnproven, {
11850
12535
  error: `no font cache at ${opts.cacheDir}`,
11851
12536
  code: "fonts-unresolved",
11852
12537
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
11853
12538
  });
11854
12539
  }
11855
- const faces = JSON.parse(readFileSync25(manifestPath2, "utf8"));
11856
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path36.resolve(opts.lock), opts.cacheDir) : null;
12540
+ const faces = JSON.parse(readFileSync26(manifestPath2, "utf8"));
12541
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path37.resolve(opts.lock), opts.cacheDir) : null;
11857
12542
  emitData(opts, { faces, lockVerdicts }, () => {
11858
12543
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
11859
12544
  `);
@@ -11897,13 +12582,13 @@ function familyMismatch(family, declared) {
11897
12582
  }
11898
12583
  function runFontsAdd(opts) {
11899
12584
  if (opts.set !== void 0) {
11900
- const declared = taskFontFamilies(path36.resolve(opts.set)) ?? [];
12585
+ const declared = taskFontFamilies(path37.resolve(opts.set)) ?? [];
11901
12586
  const mismatch = familyMismatch(opts.family, declared);
11902
12587
  if (mismatch !== void 0) {
11903
12588
  fail(opts, ExitCode.InputValidation, {
11904
12589
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
11905
12590
  code: "font-family-not-declared",
11906
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path36.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
12591
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path37.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
11907
12592
  });
11908
12593
  }
11909
12594
  } else {
@@ -11962,13 +12647,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
11962
12647
  }
11963
12648
  function runFontsAddSystem(opts) {
11964
12649
  if (opts.set !== void 0) {
11965
- const declared = taskFontFamilies(path36.resolve(opts.set)) ?? [];
12650
+ const declared = taskFontFamilies(path37.resolve(opts.set)) ?? [];
11966
12651
  const mismatch = familyMismatch(opts.family, declared);
11967
12652
  if (mismatch !== void 0) {
11968
12653
  fail(opts, ExitCode.InputValidation, {
11969
12654
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
11970
12655
  code: "font-family-not-declared",
11971
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path36.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
12656
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path37.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
11972
12657
  });
11973
12658
  }
11974
12659
  } else {
@@ -12038,24 +12723,34 @@ __export(verify_exports, {
12038
12723
  resolveComposition: () => resolveComposition,
12039
12724
  runVerify: () => runVerify
12040
12725
  });
12041
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "node:fs";
12042
- import path37 from "node:path";
12726
+ import { existsSync as existsSync31, readFileSync as readFileSync27 } from "node:fs";
12727
+ import path38 from "node:path";
12043
12728
  function interactionCoverage(behaviors) {
12044
12729
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
12045
- const interaction = behaviors.filter((b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:"));
12730
+ const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
12731
+ const contract = behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
12732
+ const interaction = behaviors.filter(
12733
+ (b) => !b.id.startsWith("prelude:") && !b.id.startsWith("parity:") && !b.id.startsWith("composition:") && !b.id.startsWith("content-prop-renders(") && !CONTRACT_CHECK_IDS.has(b.id)
12734
+ );
12046
12735
  return {
12047
12736
  interactionChecks: interaction.length,
12048
12737
  interactionPassed: interaction.filter((b) => b.pass).length,
12049
12738
  preludeChecks: behaviors.filter((b) => b.id.startsWith("prelude:")).length,
12739
+ contentChecks: content.length,
12740
+ contentPassed: content.filter((b) => b.pass).length,
12050
12741
  parityChecks: parity.length,
12051
12742
  parityPassed: parity.filter((b) => b.pass).length,
12743
+ contractChecks: contract.length,
12744
+ contractPassed: contract.filter((b) => b.pass).length,
12052
12745
  // Asymmetric on purpose (review finding F3): a parity PASS never
12053
12746
  // makes operability "verified" (parity is pixel-parity, not
12054
12747
  // someone typing or clicking — the §0j miscount), but a parity
12055
12748
  // FAIL still forbids it — "verified" printed beside a broken hover
12056
12749
  // state is the one word doing too much lifting again, from the
12057
- // other direction.
12058
- operability: interaction.length > 0 && interaction.every((b) => b.pass) && parity.every((b) => b.pass) ? "verified" : "unverified"
12750
+ // other direction. Contract checks (Cycle D review) get the SAME
12751
+ // asymmetry: ref identity and attribute probes never mint
12752
+ // "verified", a broken contract still forbids it.
12753
+ operability: interaction.length > 0 && interaction.every((b) => b.pass) && parity.every((b) => b.pass) && contract.every((b) => b.pass) ? "verified" : "unverified"
12059
12754
  };
12060
12755
  }
12061
12756
  function operabilityReport(input) {
@@ -12144,7 +12839,7 @@ function checkSummarySegments(input) {
12144
12839
  const { availability } = input;
12145
12840
  const composition = availability.unavailable !== void 0 ? `composition NOT CHECKED (${availability.short})` : `composition ${tally(input.structural)} structural, ${input.crops !== void 0 ? `${tally(input.crops)} crops` : "crops unavailable"} (roles: ${rolesSourceLabel(availability.roles)}${(availability.roles.narrowingAccepted ?? []).length > 0 ? `, ${(availability.roles.narrowingAccepted ?? []).length} narrowing(s) accepted: ${(availability.roles.narrowingAccepted ?? []).join(", ")}` : ""})`;
12146
12841
  const occ = occlusionReport(input.occlusion);
12147
- const occlusion = "unavailable" in occ ? `occlusion not applicable (${NO_OVERLAY_DECLARED})` : `occlusion ${tally(input.occlusion)}`;
12842
+ const occlusion = "unavailable" in occ ? `occlusion not applicable (${NO_OVERLAY_DECLARED})` : `occlusion ${occ.passed}/${occ.checks}${(occ.unmeasured ?? 0) > 0 ? ` measured (+${occ.unmeasured} unmeasured)` : ""}`;
12148
12843
  const operability = "short" in input.operability ? `operability UNVERIFIED (${input.operability.short})` : `operability ${input.operability.passed}/${input.operability.checks} interaction`;
12149
12844
  const cross = input.crossComposition === void 0 || input.crossComposition.rows.length === 0 && input.crossComposition.malformed === void 0 ? "" : input.crossComposition.malformed !== void 0 && input.crossComposition.rows.length === 0 ? ` \xB7 cross-composition NOT CHECKED (entries REJECTED)` : (() => {
12150
12845
  const supported = input.crossComposition.rows.filter((r) => r.status === "supported" || r.status === "stale-supported").length;
@@ -12224,7 +12919,9 @@ function occlusionReport(occlusion) {
12224
12919
  unavailable: "the bundle declares no overlay (no open-state config and no prelude popover) \u2014 the sibling-occlusion check DOES NOT APPLY and nothing about overlay stacking was measured; an empty check list is this state, not a clean bill"
12225
12920
  };
12226
12921
  }
12227
- return { checks: occlusion.length, passed: occlusion.filter((o) => o.pass).length };
12922
+ const measured = occlusion.filter((o) => o.unmeasured !== true);
12923
+ const unmeasured = occlusion.length - measured.length;
12924
+ return { checks: measured.length, passed: measured.filter((o) => o.pass).length, ...unmeasured > 0 ? { unmeasured } : {} };
12228
12925
  }
12229
12926
  function compositionReport(input) {
12230
12927
  const { availability, regions } = input;
@@ -12245,7 +12942,7 @@ function compositionReport(input) {
12245
12942
  function eyeCheck(bundleDir) {
12246
12943
  return {
12247
12944
  command: tendrilCommand(`inspect "${bundleDir}"`),
12248
- sheetPath: path37.join(bundleDir, "verify-evidence", "inspect.html"),
12945
+ sheetPath: path38.join(bundleDir, "verify-evidence", "inspect.html"),
12249
12946
  note: "magnified recorded-vs-rendered crops of every small node; scores cannot see shape. The command WRITES/refreshes the sheet at sheetPath \u2014 re-run it after every verify; an existing sheet may show stale crops."
12250
12947
  };
12251
12948
  }
@@ -12257,7 +12954,7 @@ function taskFromManifest(opts, manifest, setDir) {
12257
12954
  const adapterSlugs = Object.keys(manifest.propAdapter);
12258
12955
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
12259
12956
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
12260
- const registry = Object.values(TASKS).find((t) => path37.resolve(t.set) === path37.resolve(setDir));
12957
+ const registry = Object.values(TASKS).find((t) => path38.resolve(t.set) === path38.resolve(setDir));
12261
12958
  const authored = (() => {
12262
12959
  if (registry !== void 0) return void 0;
12263
12960
  try {
@@ -12291,24 +12988,27 @@ function taskFromManifest(opts, manifest, setDir) {
12291
12988
  // runs, so their evidence is UNKNOWN, not empty: an empty list is
12292
12989
  // spent downstream as "the recording holds no interactive pose".
12293
12990
  interactionEvidence: authored?.api.interactionEvidence,
12294
- unmappedInteractionEvidence: authored?.api.unmappedInteractionEvidence ?? []
12991
+ unmappedInteractionEvidence: authored?.api.unmappedInteractionEvidence ?? [],
12992
+ satisfiabilityDemoted: authored?.api.satisfiabilityDemoted ?? [],
12993
+ ...authored !== void 0 ? { expertContract: { archetype: authored.api.archetype.archetype, forwardRef: authored.api.apiPin.contract.forwardRef, derivedStateValues: authored.api.derivedStateTokens, publicUnionValues: authored.api.props.flatMap((p) => p.values ?? []) } } : {}
12295
12994
  };
12296
12995
  }
12297
12996
  async function runVerify(opts) {
12298
12997
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
12299
- const setOverride = opts.set !== void 0 ? path37.resolve(callerCwd, opts.set) : void 0;
12300
- opts = { ...opts, bundleDir: path37.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
12301
- if (!existsSync30(opts.bundleDir)) {
12998
+ let recordingSetDrift;
12999
+ const setOverride = opts.set !== void 0 ? path38.resolve(callerCwd, opts.set) : void 0;
13000
+ opts = { ...opts, bundleDir: path38.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
13001
+ if (!existsSync31(opts.bundleDir)) {
12302
13002
  fail(opts, ExitCode.InputValidation, {
12303
13003
  error: `bundle directory not found: ${opts.bundleDir}`,
12304
13004
  code: "bundle-missing",
12305
13005
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
12306
13006
  });
12307
13007
  }
12308
- const manifestPath2 = path37.join(opts.bundleDir, "component.json");
13008
+ const manifestPath2 = path38.join(opts.bundleDir, "component.json");
12309
13009
  let manifest;
12310
- if (existsSync30(manifestPath2)) {
12311
- const { manifest: parsed, issues } = readBundleManifest(readFileSync26(manifestPath2, "utf8"));
13010
+ if (existsSync31(manifestPath2)) {
13011
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync27(manifestPath2, "utf8"));
12312
13012
  if (issues.length > 0) {
12313
13013
  fail(opts, ExitCode.InputValidation, {
12314
13014
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -12322,6 +13022,8 @@ async function runVerify(opts) {
12322
13022
  let unmapped = [];
12323
13023
  let interactionEvidence;
12324
13024
  let unmappedInteractionEvidence = [];
13025
+ let satisfiabilityDemoted = [];
13026
+ let expertContract;
12325
13027
  let authorityConfigs;
12326
13028
  let availability = ROLES_NOT_RESOLVED;
12327
13029
  if (opts.task !== void 0) {
@@ -12336,21 +13038,21 @@ async function runVerify(opts) {
12336
13038
  task = registry;
12337
13039
  } else if (manifest !== void 0) {
12338
13040
  const resolveSetDir = (p) => {
12339
- if (path37.isAbsolute(p)) return p;
12340
- const fromRepo = path37.resolve(REPO_ROOT, p);
12341
- if (existsSync30(fromRepo)) return fromRepo;
12342
- return path37.resolve(callerCwd, p);
13041
+ if (path38.isAbsolute(p)) return p;
13042
+ const fromRepo = path38.resolve(REPO_ROOT, p);
13043
+ if (existsSync31(fromRepo)) return fromRepo;
13044
+ return path38.resolve(callerCwd, p);
12343
13045
  };
12344
13046
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
12345
- if (!existsSync30(path37.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path37.resolve(t.set) === path37.resolve(setDir))) {
13047
+ if (!existsSync31(path38.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path38.resolve(t.set) === path38.resolve(setDir))) {
12346
13048
  fail(opts, ExitCode.RecordingIncomplete, {
12347
13049
  error: `recording set not found or unmanifested: ${setDir}`,
12348
13050
  code: "recording-set-missing",
12349
13051
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
12350
13052
  });
12351
13053
  }
12352
- const registry = Object.values(TASKS).find((t) => path37.resolve(t.set) === path37.resolve(setDir));
12353
- if (registry !== void 0 && !existsSync30(path37.join(setDir, "recording-set.json"))) {
13054
+ const registry = Object.values(TASKS).find((t) => path38.resolve(t.set) === path38.resolve(setDir));
13055
+ if (registry !== void 0 && !existsSync31(path38.join(setDir, "recording-set.json"))) {
12354
13056
  const recordedSlugs = registry.configs.map((c) => c.rep);
12355
13057
  const adapterSlugs = Object.keys(manifest.propAdapter);
12356
13058
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -12367,6 +13069,8 @@ async function runVerify(opts) {
12367
13069
  authorityConfigs = built.authorityConfigs;
12368
13070
  interactionEvidence = built.interactionEvidence;
12369
13071
  unmappedInteractionEvidence = built.unmappedInteractionEvidence;
13072
+ satisfiabilityDemoted = built.satisfiabilityDemoted;
13073
+ expertContract = built.expertContract;
12370
13074
  for (const s of built.adapterOnly) warn(opts, `prop adapter maps "${s}" which is not in the recording set \u2014 ignored`);
12371
13075
  availability = resolveComposition(loadManifest(setDir).roles);
12372
13076
  if (availability.warn !== void 0) warn(opts, availability.warn);
@@ -12374,11 +13078,12 @@ async function runVerify(opts) {
12374
13078
  const hash = recordingSetHash(setDir, task.configs);
12375
13079
  if (hash !== manifest.provenance.recordingSet.hash) {
12376
13080
  warn(opts, `recording set content differs from the bundle's provenance stamp (${hash.slice(0, 12)}\u2026 vs ${manifest.provenance.recordingSet.hash.slice(0, 12)}\u2026) \u2014 scores apply to the CURRENT set`);
13081
+ recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
12377
13082
  }
12378
13083
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
12379
- const p = path37.join(opts.bundleDir, name);
12380
- if (!existsSync30(p)) continue;
12381
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync26(p)));
13084
+ const p = path38.join(opts.bundleDir, name);
13085
+ if (!existsSync31(p)) continue;
13086
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync27(p)));
12382
13087
  if (issues.length > 0) {
12383
13088
  fail(opts, ExitCode.InputValidation, {
12384
13089
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -12416,7 +13121,7 @@ async function runVerify(opts) {
12416
13121
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
12417
13122
  }
12418
13123
  const missing = task.configs.filter(
12419
- (c) => !existsSync30(path37.join(task.set, c.rep, "get_screenshot.json")) || !existsSync30(path37.join(task.set, c.rep, "get_metadata.json"))
13124
+ (c) => !existsSync31(path38.join(task.set, c.rep, "get_screenshot.json")) || !existsSync31(path38.join(task.set, c.rep, "get_metadata.json"))
12420
13125
  );
12421
13126
  if (missing.length > 0) {
12422
13127
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -12426,13 +13131,18 @@ async function runVerify(opts) {
12426
13131
  });
12427
13132
  }
12428
13133
  const bar = BARS2[opts.bar];
12429
- const evidenceDir = path37.join(opts.bundleDir, "verify-evidence");
13134
+ const evidenceDir = path38.join(opts.bundleDir, "verify-evidence");
12430
13135
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
12431
- const quality = await checkBundleQuality(opts.bundleDir, task.entry, { dir: task.set, reps: task.configs.map((c) => c.rep) }, void 0, {
12432
- recordedTruth: motionTruthFor(task.set).state === "recorded"
12433
- });
12434
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path37.join(opts.bundleDir, f)).filter((f) => existsSync30(f)).map((f) => readFileSync26(f, "utf8")).join("\n");
12435
- const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss);
13136
+ const quality = await checkBundleQuality(
13137
+ opts.bundleDir,
13138
+ task.entry,
13139
+ { dir: task.set, reps: task.configs.map((c) => c.rep) },
13140
+ void 0,
13141
+ { recordedTruth: motionTruthFor(task.set).state === "recorded" },
13142
+ expertContract
13143
+ );
13144
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path38.join(opts.bundleDir, f)).filter((f) => existsSync31(f)).map((f) => readFileSync27(f, "utf8")).join("\n");
13145
+ const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
12436
13146
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
12437
13147
  const behaviors = [...await checkBehaviors(task, opts.bundleDir), ...parity];
12438
13148
  const structural = roles !== void 0 ? await checkStructuralComposition(task, opts.bundleDir, roles) : [];
@@ -12443,7 +13153,7 @@ async function runVerify(opts) {
12443
13153
  warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
12444
13154
  }
12445
13155
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
12446
- const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path37.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
13156
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path38.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
12447
13157
  const verifyComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
12448
13158
  const composedPairs = new Set(
12449
13159
  verifyPins.pins.filter((pin) => verifyComposedChecks.filter((c) => c.id.startsWith(`composition:${pin.pairKey}:`)).every((c) => c.pass)).map((pin) => pin.pairKey)
@@ -12480,7 +13190,8 @@ async function runVerify(opts) {
12480
13190
  const occlusionFailures = occlusion.filter((o) => !o.pass);
12481
13191
  const coverage = interactionCoverage(behaviors);
12482
13192
  const operability = operabilityReport({ behaviors, interactionEvidence });
12483
- const evidenceUnverified = (interactionEvidence?.length ?? 0) > 0 && coverage.interactionChecks === 0;
13193
+ const ungatedEvidence = (interactionEvidence ?? []).filter((e) => !satisfiabilityDemoted.includes(e));
13194
+ const evidenceUnverified = ungatedEvidence.length > 0 && coverage.interactionChecks === 0;
12484
13195
  const okExceptDemotion = pixelFailures.length === 0 && behaviorFailures.length === 0 && structuralFailures.length === 0 && cropFailures.length === 0 && occlusionFailures.length === 0 && !evidenceUnverified;
12485
13196
  const certBlockedByAbsentInk = opts.bar === "cert" ? absentInkDemoted : [];
12486
13197
  const ok = okExceptDemotion && certBlockedByAbsentInk.length === 0;
@@ -12565,6 +13276,8 @@ async function runVerify(opts) {
12565
13276
  // what it is (prescribed) and what it is not (verified), plus
12566
13277
  // which truth the prescription stood on. Never a verdict input.
12567
13278
  motion: motionDisclosure(opts.bundleDir, task.set),
13279
+ ...recordingSetDrift !== void 0 ? { recordingSetDrift } : {},
13280
+ ...substitutedFamilies.length > 0 ? { substitutedFamilies } : {},
12568
13281
  eyeCheck: eyeCheck(opts.bundleDir)
12569
13282
  };
12570
13283
  emitData(opts, report, () => {
@@ -12680,7 +13393,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
12680
13393
  }
12681
13394
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
12682
13395
  `);
12683
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path37.join(opts.bundleDir, f)).filter((f) => existsSync30(f)).map((f) => readFileSync26(f, "utf8")).join("\n")));
13396
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path38.join(opts.bundleDir, f)).filter((f) => existsSync31(f)).map((f) => readFileSync27(f, "utf8")).join("\n")));
12684
13397
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
12685
13398
  process.stdout.write(`fonts: scored with Tendril-cache faces \u2014 a consuming app must provision the same families (the bundle ships fonts.css when faces are shippable; sha-pinned list in component.json requiredFonts)
12686
13399
  `);
@@ -12771,18 +13484,28 @@ __export(engine_exports, {
12771
13484
  runEngineBrief: () => runEngineBrief,
12772
13485
  runEngineScore: () => runEngineScore
12773
13486
  });
12774
- import { appendFileSync, existsSync as existsSync31, mkdirSync as mkdirSync8, readFileSync as readFileSync27, writeFileSync as writeFileSync12 } from "node:fs";
12775
- import path38 from "node:path";
13487
+ import { appendFileSync, existsSync as existsSync32, mkdirSync as mkdirSync8, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "node:fs";
13488
+ import path39 from "node:path";
12776
13489
  function resolveEngineTask(opts, callerCwd) {
12777
- const asPath = path38.resolve(callerCwd, opts.taskOrSet);
12778
- const isSet = existsSync31(path38.join(asPath, "recording-set.json"));
13490
+ const asPath = path39.resolve(callerCwd, opts.taskOrSet);
13491
+ const isSet = existsSync32(path39.join(asPath, "recording-set.json"));
12779
13492
  const registry = TASKS[opts.taskOrSet];
12780
13493
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
12781
13494
  if (isSet) {
12782
13495
  try {
12783
13496
  const authored = authorTaskFromSet(asPath);
12784
13497
  for (const d of authored.disclosures) warn(opts, d);
12785
- return { task: authored.task, name: path38.basename(asPath), ref: asPath, disclosures: authored.disclosures, interactionEvidence: authored.api.interactionEvidence, apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates } };
13498
+ return {
13499
+ task: authored.task,
13500
+ name: path39.basename(asPath),
13501
+ ref: asPath,
13502
+ disclosures: authored.disclosures,
13503
+ interactionEvidence: authored.api.interactionEvidence,
13504
+ unmappedInteractionEvidence: authored.api.unmappedInteractionEvidence,
13505
+ satisfiabilityDemoted: authored.api.satisfiabilityDemoted,
13506
+ expertContract: { archetype: authored.api.archetype.archetype, forwardRef: authored.api.apiPin.contract.forwardRef, derivedStateValues: authored.api.derivedStateTokens, publicUnionValues: authored.api.props.flatMap((p) => p.values ?? []) },
13507
+ apiPin: { props: authored.api.apiPin.props, forcedStates: authored.api.apiPin.forcedStates }
13508
+ };
12786
13509
  } catch (err) {
12787
13510
  fail(opts, ExitCode.InputValidation, {
12788
13511
  error: `cannot author a task from ${asPath}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -12809,9 +13532,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
12809
13532
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set);
12810
13533
  const segments = buildSegments(task, "files");
12811
13534
  let notRecorded;
12812
- const manifestPath2 = path38.join(task.set, "recording-set.json");
12813
- if (existsSync31(manifestPath2)) {
12814
- notRecorded = JSON.parse(readFileSync27(manifestPath2, "utf8")).notRecorded;
13535
+ const manifestPath2 = path39.join(task.set, "recording-set.json");
13536
+ if (existsSync32(manifestPath2)) {
13537
+ notRecorded = JSON.parse(readFileSync28(manifestPath2, "utf8")).notRecorded;
12815
13538
  }
12816
13539
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
12817
13540
 
@@ -12819,7 +13542,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
12819
13542
  DO NOT INVENT PIXELS FOR THESE POSES. Implement them ONLY as the composition of recorded per-axis truth (the custom-property composition rule: one paint axis sets variables, the other consumes) and list every such pose in your report as UNRECORDED-COMPOSED. Recording them is the real fix: re-plan the set (full matrix is the default) and record the missing poses.
12820
13543
  ${notRecorded}` : "";
12821
13544
  let fontProvisioning;
12822
- if (existsSync31(manifestPath2)) {
13545
+ if (existsSync32(manifestPath2)) {
12823
13546
  const missingFams = unprovisionedFamilies(task.set);
12824
13547
  const unprovided = unprovisionedFaces(task.set);
12825
13548
  const weightOnly = missingFams.length === 0;
@@ -12841,7 +13564,7 @@ ${notRecorded}` : "";
12841
13564
  };
12842
13565
  }
12843
13566
  }
12844
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path38.resolve(callerCwd, opts.library) : callerCwd]);
13567
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path39.resolve(callerCwd, opts.library) : callerCwd]);
12845
13568
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
12846
13569
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
12847
13570
 
@@ -12863,9 +13586,9 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
12863
13586
 
12864
13587
  === TASK PAYLOAD (recorded truth, verbatim) ===
12865
13588
  ${segments}`;
12866
- const payloadFile = path38.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
12867
- const candidateDirSuggestion = path38.resolve(callerCwd, `tendril-out/${name}-candidate`);
12868
- mkdirSync8(path38.dirname(payloadFile), { recursive: true });
13589
+ const payloadFile = path39.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
13590
+ const candidateDirSuggestion = path39.resolve(callerCwd, `tendril-out/${name}-candidate`);
13591
+ mkdirSync8(path39.dirname(payloadFile), { recursive: true });
12869
13592
  writeFileSync12(payloadFile, payload);
12870
13593
  emitData(
12871
13594
  opts,
@@ -12912,7 +13635,7 @@ ${segments}`;
12912
13635
  // command must search the same bundle roots the pins came
12913
13636
  // from, or the oracle and the brief describe different worlds.
12914
13637
  `Run \`${tendrilCommand(
12915
- `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path38.resolve(callerCwd, opts.library))}` : ""} --json`
13638
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path39.resolve(callerCwd, opts.library))}` : ""} --json`
12916
13639
  )}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
12917
13640
  "Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
12918
13641
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -12927,8 +13650,8 @@ ${segments}`;
12927
13650
  );
12928
13651
  }
12929
13652
  function appendScoreHistory(candidateDir, entry) {
12930
- const file = path38.join(candidateDir, "score-history.jsonl");
12931
- const starts = existsSync31(file) ? readFileSync27(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
13653
+ const file = path39.join(candidateDir, "score-history.jsonl");
13654
+ const starts = existsSync32(file) ? readFileSync28(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
12932
13655
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
12933
13656
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
12934
13657
  `);
@@ -12936,9 +13659,9 @@ function appendScoreHistory(candidateDir, entry) {
12936
13659
  async function runEngineScore(opts) {
12937
13660
  requireEntitlement(opts);
12938
13661
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
12939
- const candidateDir = path38.resolve(callerCwd, opts.candidateDir);
12940
- const { task, name, apiPin, interactionEvidence } = resolveEngineTask(opts, callerCwd);
12941
- if (!existsSync31(candidateDir)) {
13662
+ const candidateDir = path39.resolve(callerCwd, opts.candidateDir);
13663
+ const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
13664
+ if (!existsSync32(candidateDir)) {
12942
13665
  fail(opts, ExitCode.InputValidation, {
12943
13666
  error: `candidate directory not found: ${candidateDir}`,
12944
13667
  code: "candidate-missing",
@@ -12963,10 +13686,10 @@ async function runEngineScore(opts) {
12963
13686
  for (const g of missingWeights(task.set)) {
12964
13687
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
12965
13688
  }
12966
- if (opts.rebind !== true && existsSync31(path38.join(candidateDir, "component.json"))) {
13689
+ if (opts.rebind !== true && existsSync32(path39.join(candidateDir, "component.json"))) {
12967
13690
  const prior = (() => {
12968
13691
  try {
12969
- const read = readBundleManifest(readFileSync27(path38.join(candidateDir, "component.json"), "utf8"));
13692
+ const read = readBundleManifest(readFileSync28(path39.join(candidateDir, "component.json"), "utf8"));
12970
13693
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
12971
13694
  } catch {
12972
13695
  return { unreadable: true };
@@ -12988,18 +13711,25 @@ async function runEngineScore(opts) {
12988
13711
  }
12989
13712
  }
12990
13713
  const bar = BARS3[opts.bar];
12991
- const evidenceDir = path38.join(candidateDir, "verify-evidence");
13714
+ const evidenceDir = path39.join(candidateDir, "verify-evidence");
12992
13715
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
12993
13716
  const parity = await checkHoverParity(task, candidateDir, task.configs);
12994
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path38.resolve(callerCwd, opts.library) : callerCwd]);
13717
+ const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
13718
+ const occlusionFailures = occlusionRows.filter((o) => !o.pass);
13719
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path39.resolve(callerCwd, opts.library) : callerCwd]);
12995
13720
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
12996
13721
  const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
12997
13722
  const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
12998
13723
  const obj = objective(scores, behaviors);
12999
13724
  const total = scores.length + behaviors.length;
13000
- const quality = await checkBundleQuality(candidateDir, task.entry, { dir: task.set, reps: task.configs.map((c) => c.rep) }, void 0, {
13001
- recordedTruth: motionTruthFor(task.set).state === "recorded"
13002
- });
13725
+ const quality = await checkBundleQuality(
13726
+ candidateDir,
13727
+ task.entry,
13728
+ { dir: task.set, reps: task.configs.map((c) => c.rep) },
13729
+ void 0,
13730
+ { recordedTruth: motionTruthFor(task.set).state === "recorded" },
13731
+ expertContract
13732
+ );
13003
13733
  const qualityFeedback = quality.findings.length === 0 && !quality.tokensAbsent ? "" : `
13004
13734
 
13005
13735
  QUALITY (does not affect the bar \u2014 fix alongside the failing configs):
@@ -13008,7 +13738,7 @@ ${[
13008
13738
  ...quality.tokensAbsent ? ["- no design tokens: no tokens.css and no var(--\u2026) reference; every value is hardcoded"] : []
13009
13739
  ].join("\n")}`;
13010
13740
  const absentAtCertBar = opts.bar === "cert" ? scores.filter((sc) => (sc.absentInk?.length ?? 0) > 0).map((sc) => sc.rep) : [];
13011
- const allPass = obj[0] === total && total > 0 && absentAtCertBar.length === 0;
13741
+ const allPass = obj[0] === total && total > 0 && absentAtCertBar.length === 0 && occlusionFailures.length === 0;
13012
13742
  const certBar = BARS3["cert"];
13013
13743
  const parityDemoted = new Set(parity.filter((p) => !p.pass).map((p) => p.id.replace(/^parity:/, "")));
13014
13744
  const certifiedReps = substitutedFamilies.length > 0 ? [] : scores.filter((sc) => tierOf(sc, certBar) === "certified" && !parityDemoted.has(sc.rep) && (sc.absentInk === void 0 || sc.absentInk.length === 0)).map((sc) => sc.rep);
@@ -13025,11 +13755,14 @@ METRIC DEADBAND (read before iterating on near-misses): the scored similarity/in
13025
13755
  const stampNotice = `
13026
13756
 
13027
13757
  PROVENANCE STAMP: this scoring call itself (the Tendril CLI) just wrote/refreshed a comment on line 1 of styles.css carrying these scores, marked non-authoritative. If your editor or host reports styles.css was modified externally, that modification is this scorer \u2014 expected, not tampering. Keep the comment; it self-invalidates on any edit and \`tendril verify\` recomputes it.`;
13758
+ const occlusionFeedback = occlusionFailures.length > 0 ? `
13759
+
13760
+ OCCLUSION FAILURES (these block allPass and verify GATES on them): ${occlusionFailures.map((o) => `${o.id}${o.detail !== void 0 ? ` \u2014 ${o.detail}` : ""}`).join("; ")}` : "";
13028
13761
  const compositionFeedback = scorePins.issues.length > 0 ? `
13029
13762
 
13030
13763
  COMPOSITION PINS (ADR-013): ${scorePins.issues.length} confirmed pair(s) could not be pinned \u2014 verify will disclose each as confirmed-but-not-composed; not fixable by editing the candidate:
13031
13764
  ${scorePins.issues.map((i) => `- ${i}`).join("\n")}` : "";
13032
- const feedback = buildFeedback(scores, behaviors, bar, "files") + certificationFeedback + compositionFeedback + stampNotice + qualityFeedback;
13765
+ const feedback = buildFeedback(scores, behaviors, bar, "files") + certificationFeedback + compositionFeedback + occlusionFeedback + stampNotice + qualityFeedback;
13033
13766
  const emitted = emitBundleV1({
13034
13767
  bundleDir: candidateDir,
13035
13768
  task,
@@ -13055,7 +13788,7 @@ ${scorePins.issues.map((i) => `- ${i}`).join("\n")}` : "";
13055
13788
  });
13056
13789
  const coverage = interactionCoverage(behaviors);
13057
13790
  const operability = operabilityReport({ behaviors, interactionEvidence });
13058
- const evidenceUnverified = (interactionEvidence?.length ?? 0) > 0 && coverage.interactionChecks === 0;
13791
+ const evidenceUnverified = (interactionEvidence ?? []).filter((e) => !(satisfiabilityDemoted ?? []).includes(e)).length > 0 && coverage.interactionChecks === 0;
13059
13792
  const scoreMotion = motionDisclosure(candidateDir, task.set);
13060
13793
  emitData(
13061
13794
  opts,
@@ -13103,6 +13836,13 @@ ${scorePins.issues.map((i) => `- ${i}`).join("\n")}` : "";
13103
13836
  // ADR-016 §3: same disclosure as verify — the oracle must say
13104
13837
  // what verify will say about motion too.
13105
13838
  motion: scoreMotion,
13839
+ // Cycle B M3 + M2: occlusion in the oracle's JSON, and the
13840
+ // pixel-only poses on the VERDICT surface (they rode stderr
13841
+ // while verify's report carried them — the run-22 focus channel,
13842
+ // now fixed on the score side too).
13843
+ occlusion: occlusionRows,
13844
+ occlusionCheck: occlusionReport(occlusionRows),
13845
+ ...interactionEvidenceUnmapped !== void 0 && interactionEvidenceUnmapped.length > 0 ? { pixelOnlyInteractionPoses: interactionEvidenceUnmapped } : {},
13106
13846
  allPass,
13107
13847
  environment: { ...environmentStamp(taskFontFamilies(task.set)), ruler: cliVersion() },
13108
13848
  ...lcdTextEnabled() ? { environmentOverrides: ["enable-lcd-text"] } : {},
@@ -13118,9 +13858,15 @@ ${scorePins.issues.map((i) => `- ${i}`).join("\n")}` : "";
13118
13858
  for (const b of behaviors) process.stdout.write(`${b.pass ? "PASS" : "FAIL"} ${b.id}${b.detail !== void 0 ? ` [${b.detail}]` : ""}
13119
13859
  `);
13120
13860
  for (const i of scorePins.issues) process.stdout.write(`PIN ISSUE ${i}
13861
+ `);
13862
+ for (const o of occlusionRows) process.stdout.write(`${o.pass ? "PASS" : "FAIL"} ${o.id}${o.detail !== void 0 ? ` [${o.detail}]` : ""}
13121
13863
  `);
13122
13864
  if (scoreMotion.present) process.stdout.write(`MOTION ${scoreMotion.note}
13123
13865
  `);
13866
+ for (const p of interactionEvidenceUnmapped ?? []) {
13867
+ process.stdout.write(`PIXEL-ONLY ${p} \u2014 the recording proves this pose is interactive, but no operability check covers it
13868
+ `);
13869
+ }
13124
13870
  process.stdout.write(`
13125
13871
  ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${obj[1].toFixed(3)} \xB7 mean=${obj[2].toFixed(3)} \xB7 evidence: ${evidenceDir}
13126
13872
  `);
@@ -13134,6 +13880,9 @@ ${obj[0]}/${total} \xB7 certified ${certifiedReps.length}/${total} \xB7 floor=${
13134
13880
  }
13135
13881
  }
13136
13882
  );
13883
+ if (occlusionFailures.length > 0) {
13884
+ warn(opts, `${occlusionFailures.length} occlusion failure(s) block allPass (${occlusionFailures.map((o) => o.id).join(", ")}) \u2014 the OCCLUSION FAILURES feedback block carries each cause`);
13885
+ }
13137
13886
  if (absentAtCertBar.length > 0) {
13138
13887
  warn(opts, `${absentAtCertBar.length} config(s) carry absent-ink clusters (${absentAtCertBar.join(", ")}) \u2014 at --bar cert these fail the run; the MISSING FEATURES block above names each one`);
13139
13888
  }
@@ -13179,11 +13928,11 @@ var codeconnect_exports = {};
13179
13928
  __export(codeconnect_exports, {
13180
13929
  runCodeConnect: () => runCodeConnect
13181
13930
  });
13182
- import { existsSync as existsSync32, readFileSync as readFileSync28, writeFileSync as writeFileSync13 } from "node:fs";
13183
- import path39 from "node:path";
13931
+ import { existsSync as existsSync33, readFileSync as readFileSync29, writeFileSync as writeFileSync13 } from "node:fs";
13932
+ import path40 from "node:path";
13184
13933
  function runCodeConnect(opts) {
13185
13934
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
13186
- const bundleDir = path39.resolve(callerCwd, opts.bundleDir);
13935
+ const bundleDir = path40.resolve(callerCwd, opts.bundleDir);
13187
13936
  let url;
13188
13937
  try {
13189
13938
  url = new URL(opts.figmaUrl);
@@ -13199,7 +13948,7 @@ function runCodeConnect(opts) {
13199
13948
  }
13200
13949
  let manifest;
13201
13950
  try {
13202
- const read = readBundleManifest(readFileSync28(path39.join(bundleDir, "component.json"), "utf8"));
13951
+ const read = readBundleManifest(readFileSync29(path40.join(bundleDir, "component.json"), "utf8"));
13203
13952
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
13204
13953
  manifest = read.manifest;
13205
13954
  } catch (err) {
@@ -13209,8 +13958,8 @@ function runCodeConnect(opts) {
13209
13958
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
13210
13959
  });
13211
13960
  }
13212
- const setDir = path39.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
13213
- if (!existsSync32(path39.join(setDir, "recording-set.json"))) {
13961
+ const setDir = path40.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
13962
+ if (!existsSync33(path40.join(setDir, "recording-set.json"))) {
13214
13963
  fail(opts, ExitCode.InputValidation, {
13215
13964
  error: `recording set not found at ${setDir}`,
13216
13965
  code: "codeconnect-no-set",
@@ -13231,10 +13980,10 @@ function runCodeConnect(opts) {
13231
13980
  const component = api.component;
13232
13981
  const recManifest = loadManifest(setDir);
13233
13982
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
13234
- const meta = path39.join(setDir, r.slug, "get_metadata.json");
13235
- if (!existsSync32(meta)) return void 0;
13983
+ const meta = path40.join(setDir, r.slug, "get_metadata.json");
13984
+ if (!existsSync33(meta)) return void 0;
13236
13985
  try {
13237
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync28(meta, "utf8"))))?.[1];
13986
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync29(meta, "utf8"))))?.[1];
13238
13987
  } catch {
13239
13988
  return void 0;
13240
13989
  }
@@ -13256,6 +14005,7 @@ function runCodeConnect(opts) {
13256
14005
  }
13257
14006
  const boolProps = api.props.filter((p) => p.kind === "boolean");
13258
14007
  const pixelOnly = [];
14008
+ const derivedTokenFragments = [];
13259
14009
  const axisLines = [];
13260
14010
  const fragmentVars = [];
13261
14011
  for (const [axis, values] of Object.entries(axisDomains)) {
@@ -13270,8 +14020,11 @@ function runCodeConnect(opts) {
13270
14020
  fragment = owner.default === kv ? "" : ` ${owner.name}="${kv}"`;
13271
14021
  } else if (owner?.kind === "boolean") {
13272
14022
  fragment = ["true", "on", "yes"].includes(kv) ? ` ${owner.name}` : "";
14023
+ } else if (api.derivedPoseProps[kv] !== void 0) {
14024
+ fragment = Object.entries(api.derivedPoseProps[kv]).map(([name, v]) => ` ${name}={${JSON.stringify(v)}}`).join("");
13273
14025
  } else if (api.forcedStates.includes(kv)) {
13274
14026
  fragment = ` data-tendril-state="${kv}"`;
14027
+ if (api.derivedStateTokens.includes(kv)) derivedTokenFragments.push(`${axis}=${value}`);
13275
14028
  } else if (boolProps.some((p) => p.name === kv || kebab4(p.name) === kv)) {
13276
14029
  fragment = ` ${boolProps.find((p) => p.name === kv || kebab4(p.name) === kv).name}`;
13277
14030
  } else if (value === axisDefault) {
@@ -13284,7 +14037,10 @@ function runCodeConnect(opts) {
13284
14037
  fail(opts, ExitCode.InputValidation, {
13285
14038
  error: `axis "${axis}" value "${value}" maps to nothing in the authored API \u2014 an unmapped value silently breaks the Dev Mode snippet`,
13286
14039
  code: "codeconnect-unmapped-value",
13287
- remediation: "Record the missing pose (the full matrix is the default plan) so the authored API covers the full lattice, then re-emit."
14040
+ // Two distinct causes (review: the old text blamed the
14041
+ // recording even when the pose WAS recorded and authoring
14042
+ // simply had no mapping channel — an authoring gap is ours).
14043
+ remediation: "If this pose was never recorded, record it (the full matrix is the default plan) and re-emit. If it IS recorded, the authored API has no mapping channel for it \u2014 an authoring gap, not a recording gap: report it rather than re-recording."
13288
14044
  });
13289
14045
  }
13290
14046
  entries.push(`${q(value)}: ${q(fragment)}`);
@@ -13292,7 +14048,7 @@ function runCodeConnect(opts) {
13292
14048
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
13293
14049
  fragmentVars.push(varName);
13294
14050
  }
13295
- const entryRel = path39.relative(callerCwd, path39.join(bundleDir, manifest.entry));
14051
+ const entryRel = path40.relative(callerCwd, path40.join(bundleDir, manifest.entry));
13296
14052
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
13297
14053
  const lines = [
13298
14054
  `// url=${opts.figmaUrl}`,
@@ -13301,6 +14057,9 @@ function runCodeConnect(opts) {
13301
14057
  "// Generated by Tendril. Claims below are the bundle's self-reported verification \u2014 recompute them free and offline with: tendril verify",
13302
14058
  `// ${trust}`,
13303
14059
  ...pixelOnly.length > 0 ? [`// PIXEL-VERIFIED ONLY (no operable mapping): ${pixelOnly.join(", ")}`] : [],
14060
+ ...derivedTokenFragments.length > 0 ? [
14061
+ `// DERIVED STATES (${derivedTokenFragments.join(", ")}): the data-tendril-state fragment forces the pose for preview parity only \u2014 in real use these states derive from focus/value on the control, never from an attribute you set`
14062
+ ] : [],
13304
14063
  `import figma from 'figma'`,
13305
14064
  ``,
13306
14065
  `const instance = figma.selectedInstance`,
@@ -13313,7 +14072,7 @@ function runCodeConnect(opts) {
13313
14072
  `}`,
13314
14073
  ``
13315
14074
  ].join("\n");
13316
- const outFile = path39.resolve(callerCwd, opts.out ?? path39.join(bundleDir, `${component}.figma.ts`));
14075
+ const outFile = path40.resolve(callerCwd, opts.out ?? path40.join(bundleDir, `${component}.figma.ts`));
13317
14076
  writeFileSync13(outFile, lines);
13318
14077
  emitData(
13319
14078
  opts,
@@ -13353,17 +14112,17 @@ var init_codeconnect = __esm({
13353
14112
 
13354
14113
  // packages/mcp/src/server.ts
13355
14114
  import { createHash as createHash7 } from "node:crypto";
13356
- import { existsSync as existsSync33, mkdtempSync as mkdtempSync3, readFileSync as readFileSync29, readdirSync as readdirSync13, writeFileSync as writeFileSync14 } from "node:fs";
14115
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync3, readFileSync as readFileSync30, readdirSync as readdirSync13, writeFileSync as writeFileSync14 } from "node:fs";
13357
14116
  import os7 from "node:os";
13358
- import path40 from "node:path";
14117
+ import path41 from "node:path";
13359
14118
  import { fileURLToPath as fileURLToPath6 } from "node:url";
13360
14119
  import { z as z13 } from "zod";
13361
14120
  function sourceHash() {
13362
- const dir = path40.dirname(fileURLToPath6(import.meta.url));
14121
+ const dir = path41.dirname(fileURLToPath6(import.meta.url));
13363
14122
  const h = createHash7("sha256");
13364
14123
  for (const f of readdirSync13(dir).filter((n) => n.endsWith(".ts")).sort()) {
13365
14124
  h.update(f);
13366
- h.update(readFileSync29(path40.join(dir, f)));
14125
+ h.update(readFileSync30(path41.join(dir, f)));
13367
14126
  }
13368
14127
  return h.digest("hex").slice(0, 16);
13369
14128
  }
@@ -13371,10 +14130,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
13371
14130
  var init_server = __esm({
13372
14131
  "packages/mcp/src/server.ts"() {
13373
14132
  "use strict";
13374
- REPO_ROOT3 = path40.resolve(path40.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
13375
- CLI_BIN = path40.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
13376
- BUNDLED_CLI = path40.join(path40.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
13377
- CLI_SPAWN = existsSync33(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
14133
+ REPO_ROOT3 = path41.resolve(path41.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
14134
+ CLI_BIN = path41.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
14135
+ BUNDLED_CLI = path41.join(path41.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
14136
+ CLI_SPAWN = existsSync34(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
13378
14137
  str = (d) => z13.string().describe(d);
13379
14138
  optStr = (d) => z13.string().optional().describe(d);
13380
14139
  TOOLS = [
@@ -13405,7 +14164,7 @@ var init_server = __esm({
13405
14164
  const single = i["metadata"];
13406
14165
  const parts = i["metadataParts"];
13407
14166
  if (single !== void 0 || parts !== void 0) {
13408
- const tmp = path40.join(mkdtempSync3(path40.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
14167
+ const tmp = path41.join(mkdtempSync3(path41.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13409
14168
  if (single !== void 0) {
13410
14169
  writeFileSync14(tmp, single);
13411
14170
  argvOut.push("--metadata-raw-file", tmp);
@@ -13488,7 +14247,7 @@ var init_server = __esm({
13488
14247
  const bridge = (label, single, parts) => {
13489
14248
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
13490
14249
  if (single === void 0 && parts === void 0) return;
13491
- const tmp = path40.join(mkdtempSync3(path40.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
14250
+ const tmp = path41.join(mkdtempSync3(path41.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13492
14251
  if (single !== void 0) {
13493
14252
  writeFileSync14(tmp, single);
13494
14253
  argvOut.push(`--${label}-file`, tmp);
@@ -13536,7 +14295,7 @@ var init_server = __esm({
13536
14295
  const file = i["file"];
13537
14296
  if ([text, texts, file].filter((x) => x !== void 0).length !== 1) throw new Error("pass exactly one of `text` (single-block response), `texts` (multi-block response), or `file` (a saved envelope)");
13538
14297
  if (file !== void 0) return [...base, "--file", file];
13539
- const tmp = path40.join(mkdtempSync3(path40.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
14298
+ const tmp = path41.join(mkdtempSync3(path41.join(os7.tmpdir(), "tendril-envelope-")), "response.txt");
13540
14299
  if (text !== void 0) {
13541
14300
  writeFileSync14(tmp, text);
13542
14301
  return [...base, "--file", tmp, "--raw"];
@@ -13698,13 +14457,13 @@ __export(permissions_exports, {
13698
14457
  runPermissions: () => runPermissions,
13699
14458
  writeSelection: () => writeSelection
13700
14459
  });
13701
- import { existsSync as existsSync34, mkdirSync as mkdirSync9, readFileSync as readFileSync30, writeFileSync as writeFileSync15 } from "node:fs";
14460
+ import { existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync31, writeFileSync as writeFileSync15 } from "node:fs";
13702
14461
  import os8 from "node:os";
13703
- import path41 from "node:path";
14462
+ import path42 from "node:path";
13704
14463
  function mergeAllowlist(file, entries, denyEntries = []) {
13705
14464
  let settings = {};
13706
- if (existsSync34(file) && readFileSync30(file, "utf8").trim() !== "") {
13707
- settings = JSON.parse(readFileSync30(file, "utf8"));
14465
+ if (existsSync35(file) && readFileSync31(file, "utf8").trim() !== "") {
14466
+ settings = JSON.parse(readFileSync31(file, "utf8"));
13708
14467
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
13709
14468
  }
13710
14469
  const permissions = settings["permissions"] ??= {};
@@ -13724,7 +14483,7 @@ function mergeAllowlist(file, entries, denyEntries = []) {
13724
14483
  }
13725
14484
  if (added.length > 0 || denyAdded.length > 0) {
13726
14485
  allow.push(...added);
13727
- mkdirSync9(path41.dirname(file), { recursive: true });
14486
+ mkdirSync9(path42.dirname(file), { recursive: true });
13728
14487
  writeFileSync15(file, `${JSON.stringify(settings, null, 2)}
13729
14488
  `);
13730
14489
  }
@@ -13789,7 +14548,7 @@ async function runPermissions(flags) {
13789
14548
  }
13790
14549
  if (flags.write) {
13791
14550
  const base = process.env["INIT_CWD"] ?? process.cwd();
13792
- const file = flags.user ? path41.join(os8.homedir(), ".claude", "settings.json") : path41.join(base, ".claude", "settings.local.json");
14551
+ const file = flags.user ? path42.join(os8.homedir(), ".claude", "settings.json") : path42.join(base, ".claude", "settings.local.json");
13793
14552
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
13794
14553
  if (flags.dryRun) {
13795
14554
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -13939,8 +14698,8 @@ __export(compose_exports, {
13939
14698
  runCompose: () => runCompose
13940
14699
  });
13941
14700
  import { createHash as createHash8 } from "node:crypto";
13942
- import { existsSync as existsSync35, readFileSync as readFileSync31 } from "node:fs";
13943
- import path42 from "node:path";
14701
+ import { existsSync as existsSync36, readFileSync as readFileSync32 } from "node:fs";
14702
+ import path43 from "node:path";
13944
14703
  function substitutionPairs(edges, hostSet) {
13945
14704
  const pairs = /* @__PURE__ */ new Map();
13946
14705
  for (const e of edges) {
@@ -13969,7 +14728,7 @@ function runCompose(flags) {
13969
14728
  return;
13970
14729
  }
13971
14730
  const base = process.env["INIT_CWD"] ?? process.cwd();
13972
- const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
14731
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path43.resolve(base, d)) : [base];
13973
14732
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13974
14733
  fail(flags, ExitCode.InputValidation, {
13975
14734
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13978,7 +14737,7 @@ function runCompose(flags) {
13978
14737
  });
13979
14738
  }
13980
14739
  if (flags.set !== void 0) {
13981
- runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
14740
+ runComposeConfirm(flags, path43.resolve(base, flags.set), roots);
13982
14741
  return;
13983
14742
  }
13984
14743
  const index = buildComposeIndex(roots);
@@ -13996,7 +14755,7 @@ function runCompose(flags) {
13996
14755
  }
13997
14756
  let lastHost = "";
13998
14757
  for (const e of edges) {
13999
- const host = `${path42.basename(e.hostSet)}`;
14758
+ const host = `${path43.basename(e.hostSet)}`;
14000
14759
  if (host !== lastHost) {
14001
14760
  process.stdout.write(`
14002
14761
  ${host}
@@ -14004,7 +14763,7 @@ ${host}
14004
14763
  lastHost = host;
14005
14764
  }
14006
14765
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
14007
- const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
14766
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
14008
14767
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
14009
14768
  `);
14010
14769
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -14016,14 +14775,14 @@ ${NOTE}
14016
14775
  });
14017
14776
  }
14018
14777
  function runComposeConfirm(flags, hostSet, roots) {
14019
- if (!existsSync35(path42.join(hostSet, "recording-set.json"))) {
14778
+ if (!existsSync36(path43.join(hostSet, "recording-set.json"))) {
14020
14779
  fail(flags, ExitCode.InputValidation, {
14021
14780
  error: `no recording-set.json in ${hostSet}`,
14022
14781
  code: "no-recording-set",
14023
14782
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
14024
14783
  });
14025
14784
  }
14026
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
14785
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path43.dirname(hostSet)])];
14027
14786
  const index = buildComposeIndex(scanRoots);
14028
14787
  const edges = composeReport(index);
14029
14788
  const pairs = substitutionPairs(edges, hostSet);
@@ -14104,7 +14863,7 @@ PAIR ${p.displayName} [${p.key}]${p.figmaFile !== void 0 ? ` file ${p.figmaFile}
14104
14863
  // full recording-set hash join lands with pin authoring, where
14105
14864
  // task configs exist.)
14106
14865
  manifestSha256: Object.fromEntries(
14107
- p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash8("sha256").update(readFileSync31(path42.join(d, "recording-set.json"))).digest("hex")])
14866
+ p.partnerDirs.map((d) => [path43.relative(hostSet, d), createHash8("sha256").update(readFileSync32(path43.join(d, "recording-set.json"))).digest("hex")])
14108
14867
  )
14109
14868
  },
14110
14869
  instances: p.instances,
@@ -14145,7 +14904,7 @@ var init_compose2 = __esm({
14145
14904
  ],
14146
14905
  output: {
14147
14906
  sets: "string[] \u2014 recording sets indexed (--list)",
14148
- edges: "per-instance edges: kind (substitution | nested | ask | proposal | external), partners, pose (variant node id + per-set rep slugs), disclosures (--list)",
14907
+ edges: "per-instance edges: kind (substitution | nested | ask | proposal | external | hidden \u2014 hidden = invisible in every recorded pose, disclosed and never confirmable), partners, pose (variant node id + per-set rep slugs), disclosures (--list)",
14149
14908
  openPairs: "with --set: undecided id-backed pairs awaiting a human decision",
14150
14909
  standing: "with --set: persisted confirmations/declines (asked once)",
14151
14910
  note: "string \u2014 what this command does NOT do"
@@ -14163,24 +14922,24 @@ __export(inspect_exports, {
14163
14922
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
14164
14923
  runInspect: () => runInspect
14165
14924
  });
14166
- import { existsSync as existsSync36, readFileSync as readFileSync32, writeFileSync as writeFileSync16 } from "node:fs";
14167
- import path43 from "node:path";
14925
+ import { existsSync as existsSync37, readFileSync as readFileSync33, writeFileSync as writeFileSync16 } from "node:fs";
14926
+ import path44 from "node:path";
14168
14927
  async function runInspect(opts) {
14169
14928
  if (opts.describe) {
14170
14929
  printDescription(INSPECT_DESCRIPTION);
14171
14930
  return;
14172
14931
  }
14173
- const bundleDir = path43.resolve(opts.bundleDir);
14174
- const evidenceDir = path43.join(bundleDir, "verify-evidence");
14175
- const manifestPath2 = path43.join(bundleDir, "component.json");
14176
- if (!existsSync36(evidenceDir) || !existsSync36(manifestPath2)) {
14932
+ const bundleDir = path44.resolve(opts.bundleDir);
14933
+ const evidenceDir = path44.join(bundleDir, "verify-evidence");
14934
+ const manifestPath2 = path44.join(bundleDir, "component.json");
14935
+ if (!existsSync37(evidenceDir) || !existsSync37(manifestPath2)) {
14177
14936
  fail(opts, ExitCode.InputValidation, {
14178
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync36(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
14937
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync37(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
14179
14938
  code: "no-evidence",
14180
14939
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
14181
14940
  });
14182
14941
  }
14183
- const { manifest } = readBundleManifest(readFileSync32(manifestPath2, "utf8"));
14942
+ const { manifest } = readBundleManifest(readFileSync33(manifestPath2, "utf8"));
14184
14943
  if (manifest === void 0) {
14185
14944
  fail(opts, ExitCode.InputValidation, {
14186
14945
  error: "component.json did not parse as a bundle manifest",
@@ -14188,8 +14947,8 @@ async function runInspect(opts) {
14188
14947
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
14189
14948
  });
14190
14949
  }
14191
- const setDir = path43.resolve(opts.set ?? manifest.provenance.recordingSet.path);
14192
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync36(path43.join(evidenceDir, `${rep}-ref.png`)) && existsSync36(path43.join(evidenceDir, `${rep}-render.png`)));
14950
+ const setDir = path44.resolve(opts.set ?? manifest.provenance.recordingSet.path);
14951
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync37(path44.join(evidenceDir, `${rep}-ref.png`)) && existsSync37(path44.join(evidenceDir, `${rep}-render.png`)));
14193
14952
  if (reps.length === 0) {
14194
14953
  fail(opts, ExitCode.InputValidation, {
14195
14954
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -14200,15 +14959,15 @@ async function runInspect(opts) {
14200
14959
  let crops = 0;
14201
14960
  const sections = [];
14202
14961
  for (const rep of reps) {
14203
- const ref = new Uint8Array(readFileSync32(path43.join(evidenceDir, `${rep}-ref.png`)));
14204
- const render = new Uint8Array(readFileSync32(path43.join(evidenceDir, `${rep}-render.png`)));
14962
+ const ref = new Uint8Array(readFileSync33(path44.join(evidenceDir, `${rep}-ref.png`)));
14963
+ const render = new Uint8Array(readFileSync33(path44.join(evidenceDir, `${rep}-render.png`)));
14205
14964
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
14206
14965
  const cells = [];
14207
14966
  for (const [i, n] of nodes.entries()) {
14208
14967
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
14209
14968
  try {
14210
- writeFileSync16(path43.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
14211
- writeFileSync16(path43.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
14969
+ writeFileSync16(path44.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
14970
+ writeFileSync16(path44.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
14212
14971
  } catch {
14213
14972
  continue;
14214
14973
  }
@@ -14221,7 +14980,7 @@ async function runInspect(opts) {
14221
14980
  `<section><h2>${esc(rep)}</h2><div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
14222
14981
  );
14223
14982
  }
14224
- const sheet = path43.join(evidenceDir, "inspect.html");
14983
+ const sheet = path44.join(evidenceDir, "inspect.html");
14225
14984
  writeFileSync16(
14226
14985
  sheet,
14227
14986
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
@@ -14299,17 +15058,17 @@ __export(generate_recorded_exports, {
14299
15058
  runGenerateRecorded: () => runGenerateRecorded
14300
15059
  });
14301
15060
  import { confirm as confirm3, isCancel as isCancel3 } from "@clack/prompts";
14302
- import { existsSync as existsSync37, readFileSync as readFileSync33 } from "node:fs";
14303
- import path44 from "node:path";
15061
+ import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
15062
+ import path45 from "node:path";
14304
15063
  async function runGenerateRecorded(opts) {
14305
15064
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
14306
- const outDirAbs = path44.resolve(callerCwd, opts.out);
14307
- const recordedAsPath = path44.resolve(callerCwd, opts.recorded);
15065
+ const outDirAbs = path45.resolve(callerCwd, opts.out);
15066
+ const recordedAsPath = path45.resolve(callerCwd, opts.recorded);
14308
15067
  let task;
14309
15068
  let taskName;
14310
15069
  let authoredApi;
14311
15070
  let composition;
14312
- const isSet = existsSync37(path44.join(recordedAsPath, "recording-set.json"));
15071
+ const isSet = existsSync38(path45.join(recordedAsPath, "recording-set.json"));
14313
15072
  const registry = TASKS[opts.recorded];
14314
15073
  if (registry !== void 0 && !isSet) {
14315
15074
  task = registry;
@@ -14318,7 +15077,7 @@ async function runGenerateRecorded(opts) {
14318
15077
  try {
14319
15078
  const authored = authorTaskFromSet(recordedAsPath);
14320
15079
  task = authored.task;
14321
- taskName = path44.basename(recordedAsPath);
15080
+ taskName = path45.basename(recordedAsPath);
14322
15081
  authoredApi = authored.api;
14323
15082
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
14324
15083
  if (roles.success) composition = roles.data;
@@ -14352,7 +15111,7 @@ async function runGenerateRecorded(opts) {
14352
15111
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
14353
15112
  }
14354
15113
  const missing = task.configs.filter(
14355
- (c) => !existsSync37(path44.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path44.join(task.set, c.rep, "get_metadata.json")) || !existsSync37(path44.join(task.set, c.rep, "get_design_context.json"))
15114
+ (c) => !existsSync38(path45.join(task.set, c.rep, "get_screenshot.json")) || !existsSync38(path45.join(task.set, c.rep, "get_metadata.json")) || !existsSync38(path45.join(task.set, c.rep, "get_design_context.json"))
14356
15115
  );
14357
15116
  if (missing.length > 0) {
14358
15117
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -14422,8 +15181,8 @@ async function runGenerateRecorded(opts) {
14422
15181
  ` : `${line}
14423
15182
  `);
14424
15183
  if (opts.dryRun) {
14425
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path44.join(outDirAbs, taskName) }, () => {
14426
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path44.join(outDirAbs, taskName)})
15184
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path45.join(outDirAbs, taskName) }, () => {
15185
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path45.join(outDirAbs, taskName)})
14427
15186
  `);
14428
15187
  });
14429
15188
  return;
@@ -14446,10 +15205,10 @@ async function runGenerateRecorded(opts) {
14446
15205
  });
14447
15206
  }
14448
15207
  }
14449
- const bundleDir = path44.join(outDirAbs, taskName);
14450
- if (existsSync37(path44.join(bundleDir, "component.json"))) {
15208
+ const bundleDir = path45.join(outDirAbs, taskName);
15209
+ if (existsSync38(path45.join(bundleDir, "component.json"))) {
14451
15210
  try {
14452
- const prior = readBundleManifest(readFileSync33(path44.join(bundleDir, "component.json"), "utf8")).manifest;
15211
+ const prior = readBundleManifest(readFileSync34(path45.join(bundleDir, "component.json"), "utf8")).manifest;
14453
15212
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
14454
15213
  fail(opts, ExitCode.InputValidation, {
14455
15214
  error: `${bundleDir} already holds a bundle bound to recording set "${prior.provenance.recordingSet.path}" \u2014 generating here against a different set would silently rewrite its verification identity`,
@@ -14615,7 +15374,7 @@ init_invocation();
14615
15374
  init_output();
14616
15375
  import { intro, isCancel, outro, password } from "@clack/prompts";
14617
15376
  import fs from "node:fs";
14618
- import path26 from "node:path";
15377
+ import path27 from "node:path";
14619
15378
  var INIT_DESCRIPTION = {
14620
15379
  name: "init",
14621
15380
  summary: "Configure Figma and OpenRouter credentials in .env (idempotent).",
@@ -14652,7 +15411,7 @@ async function runInit(flags) {
14652
15411
  printDescription(INIT_DESCRIPTION);
14653
15412
  return;
14654
15413
  }
14655
- const envPath = path26.resolve(process.cwd(), ".env");
15414
+ const envPath = path27.resolve(process.cwd(), ".env");
14656
15415
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
14657
15416
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
14658
15417
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -14673,7 +15432,7 @@ async function runInit(flags) {
14673
15432
  next.set(ENV_KEYS.figma, figmaToken);
14674
15433
  next.set(ENV_KEYS.openrouter, openrouterKey);
14675
15434
  const changed = existing.get(ENV_KEYS.figma) !== next.get(ENV_KEYS.figma) || existing.get(ENV_KEYS.openrouter) !== next.get(ENV_KEYS.openrouter);
14676
- const gitignorePath = path26.resolve(process.cwd(), ".gitignore");
15435
+ const gitignorePath = path27.resolve(process.cwd(), ".gitignore");
14677
15436
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
14678
15437
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
14679
15438
  if (flags.dryRun) {
@@ -14729,14 +15488,14 @@ init_invocation();
14729
15488
  init_output();
14730
15489
  init_entitlement();
14731
15490
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
14732
- import { readFileSync as readFileSync17, readdirSync as readdirSync7, existsSync as existsSync21 } from "node:fs";
15491
+ import { readFileSync as readFileSync18, readdirSync as readdirSync7, existsSync as existsSync22 } from "node:fs";
14733
15492
 
14734
15493
  // packages/cli/src/pipeline.ts
14735
15494
  init_src2();
14736
15495
  init_src5();
14737
15496
  init_src4();
14738
15497
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
14739
- import path27 from "node:path";
15498
+ import path28 from "node:path";
14740
15499
 
14741
15500
  // packages/cli/src/assets-module.ts
14742
15501
  init_src();
@@ -15072,7 +15831,7 @@ async function runGenerationPipeline(input) {
15072
15831
  });
15073
15832
  const written = [];
15074
15833
  if (!input.dryRun) {
15075
- const dir = path27.resolve(input.outDir, semantics.componentName);
15834
+ const dir = path28.resolve(input.outDir, semantics.componentName);
15076
15835
  mkdirSync5(dir, { recursive: true });
15077
15836
  const files = {
15078
15837
  // Bundle-local tokens: THE emission the component's CSS resolves
@@ -15096,13 +15855,13 @@ async function runGenerationPipeline(input) {
15096
15855
  `
15097
15856
  };
15098
15857
  for (const [name, content] of Object.entries(files)) {
15099
- const filePath = path27.join(dir, name);
15858
+ const filePath = path28.join(dir, name);
15100
15859
  writeFileSync8(filePath, content);
15101
15860
  written.push(filePath);
15102
15861
  }
15103
15862
  for (const artifact of emitTokenArtifacts(input.mapping)) {
15104
- const filePath = path27.resolve(input.outDir, artifact.path);
15105
- mkdirSync5(path27.dirname(filePath), { recursive: true });
15863
+ const filePath = path28.resolve(input.outDir, artifact.path);
15864
+ mkdirSync5(path28.dirname(filePath), { recursive: true });
15106
15865
  writeFileSync8(filePath, artifact.content);
15107
15866
  written.push(filePath);
15108
15867
  }
@@ -15161,7 +15920,7 @@ var GENERATE_DESCRIPTION = {
15161
15920
  function resolveProvidedSource(flags, contextFile) {
15162
15921
  let raw;
15163
15922
  try {
15164
- raw = readFileSync17(contextFile, "utf8");
15923
+ raw = readFileSync18(contextFile, "utf8");
15165
15924
  } catch {
15166
15925
  fail(flags, ExitCode.InputValidation, {
15167
15926
  error: `Cannot read context file "${contextFile}".`,
@@ -15281,11 +16040,11 @@ token mapping (${mapping.flat.length} variables):
15281
16040
  let initialCode;
15282
16041
  let initialSemantics;
15283
16042
  try {
15284
- if (existsSync21(flags.out)) {
16043
+ if (existsSync22(flags.out)) {
15285
16044
  for (const entry of readdirSync7(flags.out)) {
15286
16045
  const cjPath = `${flags.out}/${entry}/component.json`;
15287
- if (!existsSync21(cjPath)) continue;
15288
- const cj = JSON.parse(readFileSync17(cjPath, "utf8"));
16046
+ if (!existsSync22(cjPath)) continue;
16047
+ const cj = JSON.parse(readFileSync18(cjPath, "utf8"));
15289
16048
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
15290
16049
  previousApi = JSON.stringify({
15291
16050
  componentName: cj.name,
@@ -15293,14 +16052,14 @@ token mapping (${mapping.flat.length} variables):
15293
16052
  });
15294
16053
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
15295
16054
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
15296
- if (flags.refine && existsSync21(tsxPath) && existsSync21(cssPath)) {
16055
+ if (flags.refine && existsSync22(tsxPath) && existsSync22(cssPath)) {
15297
16056
  initialCode = {
15298
- tsx: readFileSync17(tsxPath, "utf8"),
15299
- css: readFileSync17(cssPath, "utf8")
16057
+ tsx: readFileSync18(tsxPath, "utf8"),
16058
+ css: readFileSync18(cssPath, "utf8")
15300
16059
  };
15301
16060
  const semPath = `${flags.out}/${entry}/semantics.json`;
15302
- if (existsSync21(semPath)) {
15303
- initialSemantics = JSON.parse(readFileSync17(semPath, "utf8"));
16061
+ if (existsSync22(semPath)) {
16062
+ initialSemantics = JSON.parse(readFileSync18(semPath, "utf8"));
15304
16063
  }
15305
16064
  }
15306
16065
  break;