qfai 1.4.37 → 1.4.38

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.
@@ -31,6 +31,14 @@ The contract must describe both screen structure and minimum mockable behavior.
31
31
  3. `.qfai/evidence/prototyping.json` (`uiFidelity` snapshot)
32
32
  - If label text is intentionally hidden (icon-only, aria-only), add stable `data-qfai` markers and document mapping in the contract comments.
33
33
 
34
+ ### `data-qfai` marker convention
35
+
36
+ - Recommended marker value: `CONTRACT_ID:ELEMENT_ID` (example: `data-qfai="CON-UI-0001:search_input"`).
37
+ - Use `elements[].id` (stable ID) for the marker suffix, not `elements[].label`.
38
+ - Even when label text is not visible in the UI, markers ensure fidelity coverage.
39
+ - autogen generates expected markers from `elements[].id` automatically.
40
+ - Legacy format (`CONTRACT_ID:ELEMENT_LABEL`) is accepted for backward compatibility but new implementations should use the id-based format.
41
+
34
42
  ## Mockable prototype minimum (L2)
35
43
 
36
44
  Add `prototype` at the top level.
@@ -1589,8 +1589,8 @@ var import_promises7 = require("fs/promises");
1589
1589
  var import_node_path8 = __toESM(require("path"), 1);
1590
1590
  var import_node_url2 = require("url");
1591
1591
  async function resolveToolVersion() {
1592
- if ("1.4.37".length > 0) {
1593
- return "1.4.37";
1592
+ if ("1.4.38".length > 0) {
1593
+ return "1.4.38";
1594
1594
  }
1595
1595
  try {
1596
1596
  const packagePath = resolvePackageJsonPath();
@@ -3648,15 +3648,29 @@ function buildUiFidelityScreens(expectedScreens, crawledRoutes, mockPathResults)
3648
3648
  const hasPass = mockPath?.entries.some(
3649
3649
  (entry) => entry.status.toLowerCase() === "pass"
3650
3650
  ) ?? false;
3651
- const expectedMarkers = screen.labels.map(
3652
- (label) => `${screen.uiContractId}:${label}`
3653
- );
3654
- const crawledMarkers = crawl?.markers ?? [];
3655
- const foundMarkers = expectedMarkers.filter(
3656
- (marker) => crawledMarkers.includes(marker)
3657
- );
3651
+ const expectedMarkers = screen.elementIds.map(
3652
+ (id) => `${screen.uiContractId}:${id}`
3653
+ );
3654
+ const crawledMarkersSet = new Set(crawl?.markers ?? []);
3655
+ const labelToIdMarker = /* @__PURE__ */ new Map();
3656
+ for (const pair of screen.elementPairs) {
3657
+ const labelMarker = `${screen.uiContractId}:${pair.label}`;
3658
+ const idMarker = `${screen.uiContractId}:${pair.id}`;
3659
+ if (labelMarker !== idMarker) {
3660
+ labelToIdMarker.set(labelMarker, idMarker);
3661
+ }
3662
+ }
3663
+ const foundMarkers = expectedMarkers.filter((marker) => {
3664
+ if (crawledMarkersSet.has(marker)) return true;
3665
+ for (const [labelMarker, idMarker] of labelToIdMarker) {
3666
+ if (idMarker === marker && crawledMarkersSet.has(labelMarker)) {
3667
+ return true;
3668
+ }
3669
+ }
3670
+ return false;
3671
+ });
3658
3672
  const missingMarkers = expectedMarkers.filter(
3659
- (marker) => !crawledMarkers.includes(marker)
3673
+ (marker) => !foundMarkers.includes(marker)
3660
3674
  );
3661
3675
  return {
3662
3676
  route: screen.route,
@@ -3664,7 +3678,8 @@ function buildUiFidelityScreens(expectedScreens, crawledRoutes, mockPathResults)
3664
3678
  expected: {
3665
3679
  elements: screen.elementCount,
3666
3680
  actions: screen.actionIds.length,
3667
- labels: screen.labels
3681
+ labels: screen.labels,
3682
+ ids: screen.elementIds
3668
3683
  },
3669
3684
  found: {
3670
3685
  labels: coverage.found,
@@ -3784,6 +3799,8 @@ function collectContractScreens(contractId, doc, mockPathIds) {
3784
3799
  route,
3785
3800
  uiContractId: contractId,
3786
3801
  expectedLabels: elements.labels,
3802
+ elementIds: elements.ids,
3803
+ elementPairs: elements.pairs,
3787
3804
  elementCount: elements.count,
3788
3805
  actionIds,
3789
3806
  mockPathIds,
@@ -3795,8 +3812,15 @@ function collectContractScreens(contractId, doc, mockPathIds) {
3795
3812
  function collectElements(value) {
3796
3813
  const entries = Array.isArray(value) ? value : [];
3797
3814
  const validEntries = entries.map((entry) => readRecord(entry)).filter((entry) => Boolean(entry));
3815
+ const ids = validEntries.map((entry) => readText(entry.id)).filter((id) => id.length > 0);
3798
3816
  const labels = validEntries.map((entry) => readText(entry.label)).filter((label) => label.length > 0);
3799
- return { labels: dedupeLabels(labels), count: validEntries.length };
3817
+ const pairs = validEntries.map((entry) => ({ id: readText(entry.id), label: readText(entry.label) })).filter((pair) => pair.id.length > 0 && pair.label.length > 0);
3818
+ return {
3819
+ ids: Array.from(new Set(ids)).sort((a, b) => a.localeCompare(b)),
3820
+ labels: dedupeLabels(labels),
3821
+ pairs,
3822
+ count: validEntries.length
3823
+ };
3800
3824
  }
3801
3825
  function collectActionIds(value) {
3802
3826
  const entries = Array.isArray(value) ? value : [];
@@ -3842,6 +3866,10 @@ function dedupeExpectedScreens(screens) {
3842
3866
  route: screen.route,
3843
3867
  uiContractId: screen.uiContractId,
3844
3868
  labels: dedupeLabels(screen.expectedLabels),
3869
+ elementIds: Array.from(new Set(screen.elementIds)).sort(
3870
+ (a, b) => a.localeCompare(b)
3871
+ ),
3872
+ elementPairs: [...screen.elementPairs],
3845
3873
  elementCount: screen.elementCount,
3846
3874
  actionIds: Array.from(new Set(screen.actionIds)).sort(
3847
3875
  (a, b) => a.localeCompare(b)
@@ -3859,6 +3887,16 @@ function dedupeExpectedScreens(screens) {
3859
3887
  ...current.labels,
3860
3888
  ...screen.expectedLabels
3861
3889
  ]);
3890
+ current.elementIds = Array.from(
3891
+ /* @__PURE__ */ new Set([...current.elementIds, ...screen.elementIds])
3892
+ ).sort((a, b) => a.localeCompare(b));
3893
+ const existingIds = new Set(current.elementPairs.map((p) => p.id));
3894
+ for (const pair of screen.elementPairs) {
3895
+ if (!existingIds.has(pair.id)) {
3896
+ current.elementPairs.push(pair);
3897
+ existingIds.add(pair.id);
3898
+ }
3899
+ }
3862
3900
  current.elementCount += screen.elementCount;
3863
3901
  current.actionIds = Array.from(
3864
3902
  /* @__PURE__ */ new Set([...current.actionIds, ...screen.actionIds])
@@ -11723,7 +11761,8 @@ async function validateUiFidelity(root, config, evidenceJsonPath, evidence) {
11723
11761
  refs,
11724
11762
  "change",
11725
11763
  [
11726
- '\u753B\u9762\u306E\u5404\u8981\u7D20\u306B data-qfai="CONTRACT_ID:ELEMENT_LABEL" \u30DE\u30FC\u30AB\u30FC\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002',
11764
+ '\u753B\u9762\u306E\u5404\u8981\u7D20\u306B data-qfai="CONTRACT_ID:ELEMENT_ID" \u30DE\u30FC\u30AB\u30FC\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002',
11765
+ '\u30DE\u30FC\u30AB\u30FC\u306E\u5024\u306F contracts/ui \u306E elements[].id \u3092\u4F7F\u3044\u307E\u3059\uFF08\u4F8B: data-qfai="CON-UI-0001:search_input"\uFF09\u3002',
11727
11766
  "autogen \u3092\u518D\u5B9F\u884C\u3057\u3001missing.markers \u304C\u7A7A\u306B\u306A\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
11728
11767
  ].join("\n")
11729
11768
  )
@@ -12033,12 +12072,14 @@ function normalizeUiFidelityExpected(value) {
12033
12072
  };
12034
12073
  }
12035
12074
  const labels = toOptionalStringArray(value.labels);
12075
+ const ids = toOptionalStringArray(value.ids);
12036
12076
  return {
12037
12077
  ok: true,
12038
12078
  value: {
12039
12079
  elements: value.elements,
12040
12080
  actions: value.actions,
12041
- ...labels ? { labels } : {}
12081
+ ...labels ? { labels } : {},
12082
+ ...ids ? { ids } : {}
12042
12083
  }
12043
12084
  };
12044
12085
  }