@stapel/listings-react 0.18.2 → 0.20.0

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 (120) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/api/generated/schema.d.ts +101 -13
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/api/listingsApi.d.ts +26 -1
  5. package/dist/api/listingsApi.d.ts.map +1 -1
  6. package/dist/api/listingsApi.js +9 -0
  7. package/dist/api/listingsApi.js.map +1 -1
  8. package/dist/api/types.d.ts +91 -6
  9. package/dist/api/types.d.ts.map +1 -1
  10. package/dist/api/types.js +32 -0
  11. package/dist/api/types.js.map +1 -1
  12. package/dist/default/GateReasonPopover.d.ts.map +1 -1
  13. package/dist/default/GateReasonPopover.js +92 -2
  14. package/dist/default/GateReasonPopover.js.map +1 -1
  15. package/dist/default/ListingCard.d.ts +32 -1
  16. package/dist/default/ListingCard.d.ts.map +1 -1
  17. package/dist/default/ListingCard.js +65 -14
  18. package/dist/default/ListingCard.js.map +1 -1
  19. package/dist/default/ListingDetailPane.d.ts.map +1 -1
  20. package/dist/default/ListingDetailPane.js +18 -9
  21. package/dist/default/ListingDetailPane.js.map +1 -1
  22. package/dist/default/ListingFeedCard.d.ts +32 -7
  23. package/dist/default/ListingFeedCard.d.ts.map +1 -1
  24. package/dist/default/ListingFeedCard.js +11 -3
  25. package/dist/default/ListingFeedCard.js.map +1 -1
  26. package/dist/default/ListingPrice.d.ts +13 -2
  27. package/dist/default/ListingPrice.d.ts.map +1 -1
  28. package/dist/default/ListingPrice.js.map +1 -1
  29. package/dist/default/ListingSerpCard.d.ts.map +1 -1
  30. package/dist/default/ListingSerpCard.js +10 -2
  31. package/dist/default/ListingSerpCard.js.map +1 -1
  32. package/dist/default/favorite.d.ts +11 -0
  33. package/dist/default/favorite.d.ts.map +1 -1
  34. package/dist/default/favorite.js +24 -9
  35. package/dist/default/favorite.js.map +1 -1
  36. package/dist/default/icons.d.ts +17 -3
  37. package/dist/default/icons.d.ts.map +1 -1
  38. package/dist/default/icons.js +2 -1
  39. package/dist/default/icons.js.map +1 -1
  40. package/dist/default/index.d.ts +1 -1
  41. package/dist/default/index.d.ts.map +1 -1
  42. package/dist/default/index.js +4 -1
  43. package/dist/default/index.js.map +1 -1
  44. package/dist/headless/Engagement.d.ts +62 -0
  45. package/dist/headless/Engagement.d.ts.map +1 -0
  46. package/dist/headless/Engagement.js +116 -0
  47. package/dist/headless/Engagement.js.map +1 -0
  48. package/dist/headless/Favorites.d.ts +50 -0
  49. package/dist/headless/Favorites.d.ts.map +1 -1
  50. package/dist/headless/Favorites.js +63 -3
  51. package/dist/headless/Favorites.js.map +1 -1
  52. package/dist/headless/ListingDetail.d.ts +21 -1
  53. package/dist/headless/ListingDetail.d.ts.map +1 -1
  54. package/dist/headless/ListingDetail.js +39 -3
  55. package/dist/headless/ListingDetail.js.map +1 -1
  56. package/dist/i18n/es.d.ts.map +1 -1
  57. package/dist/i18n/es.js +4 -0
  58. package/dist/i18n/es.js.map +1 -1
  59. package/dist/i18n/generated/errors.es.gen.d.ts +1 -1
  60. package/dist/i18n/generated/errors.es.gen.js +1 -1
  61. package/dist/i18n/generated/errors.gen.d.ts +12 -0
  62. package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
  63. package/dist/i18n/generated/errors.gen.js +6 -0
  64. package/dist/i18n/generated/errors.gen.js.map +1 -1
  65. package/dist/i18n/generated/errors.ru.gen.d.ts +1 -1
  66. package/dist/i18n/generated/errors.ru.gen.js +1 -1
  67. package/dist/i18n/keys.d.ts +7 -0
  68. package/dist/i18n/keys.d.ts.map +1 -1
  69. package/dist/i18n/keys.js +8 -0
  70. package/dist/i18n/keys.js.map +1 -1
  71. package/dist/i18n/ru.d.ts.map +1 -1
  72. package/dist/i18n/ru.js +6 -0
  73. package/dist/i18n/ru.js.map +1 -1
  74. package/dist/index.d.ts +6 -3
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/model/engagement.d.ts +85 -0
  79. package/dist/model/engagement.d.ts.map +1 -0
  80. package/dist/model/engagement.js +59 -0
  81. package/dist/model/engagement.js.map +1 -0
  82. package/dist/model/queries.d.ts +28 -1
  83. package/dist/model/queries.d.ts.map +1 -1
  84. package/dist/model/queries.js +36 -0
  85. package/dist/model/queries.js.map +1 -1
  86. package/dist/model/queryKeys.d.ts +12 -13
  87. package/dist/model/queryKeys.d.ts.map +1 -1
  88. package/dist/model/queryKeys.js +15 -0
  89. package/dist/model/queryKeys.js.map +1 -1
  90. package/llms.txt +6 -3
  91. package/manifest.json +71 -3
  92. package/nav-manifest.json +1 -1
  93. package/package.json +8 -8
  94. package/src/analytics/generated/events.json +1 -1
  95. package/src/api/generated/schema.ts +101 -13
  96. package/src/api/listingsApi.ts +39 -0
  97. package/src/api/types.ts +115 -6
  98. package/src/default/GateReasonPopover.tsx +114 -7
  99. package/src/default/ListingCard.tsx +84 -10
  100. package/src/default/ListingDetailPane.tsx +77 -20
  101. package/src/default/ListingFeedCard.tsx +45 -8
  102. package/src/default/ListingPrice.tsx +13 -2
  103. package/src/default/ListingSerpCard.tsx +11 -1
  104. package/src/default/favorite.tsx +49 -21
  105. package/src/default/icons.tsx +21 -3
  106. package/src/default/index.ts +4 -0
  107. package/src/headless/Engagement.tsx +177 -0
  108. package/src/headless/Favorites.tsx +93 -3
  109. package/src/headless/ListingDetail.tsx +64 -4
  110. package/src/i18n/es.ts +7 -0
  111. package/src/i18n/generated/errors.es.gen.ts +1 -1
  112. package/src/i18n/generated/errors.gen.ts +6 -0
  113. package/src/i18n/generated/errors.json +14 -0
  114. package/src/i18n/generated/errors.ru.gen.ts +1 -1
  115. package/src/i18n/keys.ts +8 -0
  116. package/src/i18n/ru.ts +9 -0
  117. package/src/index.ts +27 -0
  118. package/src/model/engagement.ts +118 -0
  119. package/src/model/queries.ts +41 -0
  120. package/src/model/queryKeys.ts +14 -0
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,3 @@
1
- /**
2
- * Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
3
- * Everything under the `"listings"` root so a host can invalidate the whole
4
- * module or match a single read. Explicit tuple return types satisfy
5
- * `--isolatedDeclarations`.
6
- *
7
- * THE KEY IS THE REQUEST for the paginated reads: a page is keyed on the very
8
- * parameter object the client is handed, normalized so that "no cursor" and
9
- * "an explicitly undefined cursor" cannot cache twice. Two states that
10
- * produce the same request therefore share a cache entry, and one that
11
- * produces a different request cannot silently reuse a page — the stale-page
12
- * bug (new filter, cached rows) is unwritable rather than merely avoided.
13
- */
14
1
  import type { ListingPageParams } from "../api/types.js";
15
2
  /** The normalized page cursor a list read is keyed on. */
16
3
  export interface ListingPageKey {
@@ -51,5 +38,17 @@ export declare const listingsQueryKeys: {
51
38
  allFavorites(): readonly ["listings", "my", "favorites"];
52
39
  /** Every published-card PAGE, same reason. */
53
40
  allLists(): readonly ["listings", "list"];
41
+ /**
42
+ * The per-viewer overlay for one page of ids.
43
+ *
44
+ * Keyed on the NORMALIZED list (`engagementIds`: sorted, de-duplicated,
45
+ * capped) and joined into one string, which is the same value the request
46
+ * sends. The doctrine at the top of this file applied to a batch read: two
47
+ * renders asking for the same ids in a different order are asking the
48
+ * identical question — the answer is a map keyed by id and carries no order
49
+ * of its own — so they must share one cache entry and cost one request.
50
+ * Keyed on the raw array they would cost two, per re-render, per grid.
51
+ */
52
+ engagement(ids: readonly number[]): readonly ["listings", "engagement", string];
54
53
  };
55
54
  //# sourceMappingURL=queryKeys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;wBAEwB;AACxB,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAMlE;AAID,eAAO,MAAM,iBAAiB,EAAE;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,+BAA+B;IAC/B,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1E,2BAA2B;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D;;0EAEsE;IACtE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,oCAAoC;IACpC,UAAU,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACtD,gDAAgD;IAChD,WAAW,CACT,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC5D,mEAAmE;IACnE,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IACnE;;sCAEkC;IAClC,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACpE,+DAA+D;IAC/D,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACnD,8DAA8D;IAC9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC3E;8EAC0E;IAC1E,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACzD,8CAA8C;IAC9C,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;CAc3C,CAAC"}
1
+ {"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;wBAEwB;AACxB,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAMlE;AAID,eAAO,MAAM,iBAAiB,EAAE;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,+BAA+B;IAC/B,IAAI,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1E,2BAA2B;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D;;0EAEsE;IACtE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5D,oCAAoC;IACpC,UAAU,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACtD,gDAAgD;IAChD,WAAW,CACT,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;IAC5D,mEAAmE;IACnE,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,GACnB,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IACnE;;sCAEkC;IAClC,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACpE,+DAA+D;IAC/D,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACnD,8DAA8D;IAC9D,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC3E;8EAC0E;IAC1E,YAAY,IAAI,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IACzD,8CAA8C;IAC9C,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1C;;;;;;;;;;OAUG;IACH,UAAU,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;CAejF,CAAC"}
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
3
+ * Everything under the `"listings"` root so a host can invalidate the whole
4
+ * module or match a single read. Explicit tuple return types satisfy
5
+ * `--isolatedDeclarations`.
6
+ *
7
+ * THE KEY IS THE REQUEST for the paginated reads: a page is keyed on the very
8
+ * parameter object the client is handed, normalized so that "no cursor" and
9
+ * "an explicitly undefined cursor" cannot cache twice. Two states that
10
+ * produce the same request therefore share a cache entry, and one that
11
+ * produces a different request cannot silently reuse a page — the stale-page
12
+ * bug (new filter, cached rows) is unwritable rather than merely avoided.
13
+ */
14
+ import { engagementIds } from "../api/types.js";
1
15
  /** Normalize a page's parameters into its key. Exported because a test that
2
16
  * asserts "these two states hit one cache entry" must build the key the same
3
17
  * way the hook does. */
@@ -22,5 +36,6 @@ export const listingsQueryKeys = {
22
36
  validateDraft: (id) => [ROOT, "validate-draft", id],
23
37
  allFavorites: () => [ROOT, "my", "favorites"],
24
38
  allLists: () => [ROOT, "list"],
39
+ engagement: (ids) => [ROOT, "engagement", engagementIds(ids).join(",")],
25
40
  };
26
41
  //# sourceMappingURL=queryKeys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAsBA;;wBAEwB;AACxB,MAAM,UAAU,OAAO,CAAC,MAA0B;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI;QAC9B,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI;QACpC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,UAAmB,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GAkC1B;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC;IACtD,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC;IACxD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC;IACxD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IACvC,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;IACnD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;CAC/B,CAAC"}
1
+ {"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAUhD;;wBAEwB;AACxB,MAAM,UAAU,OAAO,CAAC,MAA0B;IAChD,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,IAAI;QAC9B,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI;QACpC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,IAAI,GAAG,UAAmB,CAAC;AAEjC,MAAM,CAAC,MAAM,iBAAiB,GA8C1B;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IACpC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC;IACtD,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC;IACxD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC;IACxD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC;IACvC,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,CAAC;IACnD,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,YAAY,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;CACxE,CAAC"}
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
- # @stapel/listings-react 0.18.2
1
+ # @stapel/listings-react 0.20.0
2
2
 
3
- Headless React flow pair for stapel-listings (contract >=0.12 <0.13) — business + state, zero visual opinion.
3
+ Headless React flow pair for stapel-listings (contract >=0.17 <0.18) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
5
5
  verification-403 interception, i18n engine, analytics facade, TanStack Query layer.
6
6
 
@@ -25,6 +25,7 @@ Paths are relative to `/listings/api/v1/`.
25
25
  - listings_api_v1_listings_complete_create: POST /listings/{id}/complete/
26
26
  - listings_api_v1_listings_create: POST /listings/
27
27
  - listings_api_v1_listings_destroy: DELETE /listings/{id}/
28
+ - listings_api_v1_listings_engagement_retrieve: GET /listings/engagement/
28
29
  - listings_api_v1_listings_favorite_create: POST /listings/{id}/favorite/
29
30
  - listings_api_v1_listings_list: GET /listings/
30
31
  - listings_api_v1_listings_my_counters_retrieve: GET /listings/my/counters/
@@ -47,6 +48,7 @@ Paths are relative to `/listings/api/v1/`.
47
48
  - useFavoriteListing (mutation) → favorite+unfavorite
48
49
  - useListing (query) → retrieve
49
50
  - useListingCards (query) → list
51
+ - useListingEngagement (query) → engagement
50
52
  - useListingStatus (query) → status
51
53
  - useMyCounters (query) → myCounters
52
54
  - useMyFavorites (query) → myFavorites
@@ -55,7 +57,7 @@ Paths are relative to `/listings/api/v1/`.
55
57
  - useValidateDraft (query) → validateDraft
56
58
 
57
59
  ## Errors (render t(code, params); UX from remediation)
58
- 66 keys (full catalog: manifest.json §errors). By remediation: fix_input 37 · retry 16 · verify 5 · wait_and_retry 5 · contact_support 2 · reauthenticate 1.
60
+ 68 keys (full catalog: manifest.json §errors). By remediation: fix_input 39 · retry 16 · verify 5 · wait_and_retry 5 · contact_support 2 · reauthenticate 1.
59
61
  Param-bearing keys (interpolation slots matter):
60
62
  - error.400.description_too_long [400] → fix_input {max_length}
61
63
  - error.400.description_too_short [400] → fix_input {min_length}
@@ -109,6 +111,7 @@ const { tracked } = useTracked();
109
111
  - listings.card → <ListingCard> [default|saved|visitor|grid cell|list row|no-photo] demo/ListingCard.demo.tsx
110
112
  - listings.composer → <ListingComposerPage> [default|unwired|photos-in-flight|unsupported-type] demo/ListingComposer.demo.tsx
111
113
  - listings.detail → <ListingDetailPane> [default|contact-unwired|owner|not-found] demo/ListingDetail.demo.tsx
114
+ - listings.engagement-scope → <ListingEngagementScope> [default|unscoped|visitor|failed] demo/ListingEngagementScope.demo.tsx
112
115
  - listings.favorites → <FavoritesPane> [default|paged|empty|failed|visitor] demo/Favorites.demo.tsx
113
116
  - listings.feed-card → <ListingFeedCard> [default|tile|visitor|wide] demo/ListingFeedCard.demo.tsx
114
117
  - listings.mine → <MyListingsPane> [default|no-editor|taken-down|visitor|failed] demo/MyListings.demo.tsx
package/manifest.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/listings-react",
4
- "version": "0.18.2",
4
+ "version": "0.20.0",
5
5
  "backend": {
6
6
  "module": "stapel-listings",
7
- "contract": ">=0.12 <0.13"
7
+ "contract": ">=0.17 <0.18"
8
8
  },
9
9
  "layers": [
10
10
  "api",
@@ -44,6 +44,13 @@
44
44
  "request": null,
45
45
  "response": "DeleteResponse"
46
46
  },
47
+ "listings_api_v1_listings_engagement_retrieve": {
48
+ "method": "GET",
49
+ "path": "/listings/api/v1/listings/engagement/",
50
+ "tag": "listings",
51
+ "request": null,
52
+ "response": "ListingEngagementBatch"
53
+ },
47
54
  "listings_api_v1_listings_favorite_create": {
48
55
  "method": "POST",
49
56
  "path": "/listings/api/v1/listings/{id}/favorite/",
@@ -211,6 +218,15 @@
211
218
  "{page}"
212
219
  ]
213
220
  },
221
+ "useListingEngagement": {
222
+ "kind": "query",
223
+ "operation": "engagement",
224
+ "queryKey": [
225
+ "listings",
226
+ "engagement",
227
+ "{…}"
228
+ ]
229
+ },
214
230
  "useListingStatus": {
215
231
  "kind": "query",
216
232
  "operation": "status",
@@ -479,6 +495,16 @@
479
495
  ],
480
496
  "remediation": "fix_input"
481
497
  },
498
+ "error.400.listing_location_required": {
499
+ "status": 400,
500
+ "params": [],
501
+ "remediation": "fix_input"
502
+ },
503
+ "error.400.listing_zero_price_not_allowed": {
504
+ "status": 400,
505
+ "params": [],
506
+ "remediation": "fix_input"
507
+ },
482
508
  "error.400.publish_validation_failed": {
483
509
  "status": 400,
484
510
  "params": [],
@@ -713,6 +739,26 @@
713
739
  ],
714
740
  "source": "demo/ListingDetail.demo.tsx"
715
741
  },
742
+ {
743
+ "id": "listings.engagement-scope",
744
+ "title": "Engagement overlay",
745
+ "description": "One request for a whole page of cards, read by every card in it. A storefront's feed and SERP are drawn from the search index, whose stored document can carry neither a per-reader flag nor a fast-moving counter — so without this scope the already-seen rows and the saved ones are indistinguishable from the rest, on exactly the two screens the state exists for. The container opens a scope with the ids it just rendered, GET /listings/engagement/ answers once, and each card looks itself up and lays the entry over its own row. No scope, a missing id, a read in flight and a read that FAILED are all the same silent no-op: the card draws from its row and the grid keeps working.",
746
+ "component": "ListingEngagementScope",
747
+ "covers": [
748
+ "ListingEngagementContext",
749
+ "ListingsProvider"
750
+ ],
751
+ "tokens": [
752
+ "text-muted"
753
+ ],
754
+ "variants": [
755
+ "default",
756
+ "unscoped",
757
+ "visitor",
758
+ "failed"
759
+ ],
760
+ "source": "demo/ListingEngagementScope.demo.tsx"
761
+ },
716
762
  {
717
763
  "id": "listings.favorites",
718
764
  "title": "Favourites",
@@ -869,6 +915,8 @@
869
915
  "error.400.invalid_ad_id",
870
916
  "error.400.listing_feature_not_allowed",
871
917
  "error.400.listing_invalid_status_filter",
918
+ "error.400.listing_location_required",
919
+ "error.400.listing_zero_price_not_allowed",
872
920
  "error.400.publish_validation_failed",
873
921
  "error.400.validation_error",
874
922
  "error.400.verification_failed",
@@ -983,6 +1031,7 @@
983
1031
  "listings.detail.stock",
984
1032
  "listings.detail.take_down",
985
1033
  "listings.detail.unreadable_features",
1034
+ "listings.detail.views",
986
1035
  "listings.detail.withdrawn",
987
1036
  "listings.error.unknown",
988
1037
  "listings.favorites.empty",
@@ -1051,6 +1100,7 @@
1051
1100
  "Favorites",
1052
1101
  "IMAGES_FIELD",
1053
1102
  "LISTINGS_ELEVATION_ACTIONS",
1103
+ "LISTINGS_ENGAGEMENT_BATCH_LIMIT",
1054
1104
  "LISTINGS_ERRORS",
1055
1105
  "LISTINGS_ERROR_CODES",
1056
1106
  "LISTINGS_FLOWS",
@@ -1064,6 +1114,8 @@
1064
1114
  "LOCATION_FIELD",
1065
1115
  "ListingComposer",
1066
1116
  "ListingDetail",
1117
+ "ListingEngagementContext",
1118
+ "ListingEngagementScope",
1067
1119
  "ListingsProvider",
1068
1120
  "ListingsRuntimeContext",
1069
1121
  "MODERATION_STATUSES",
@@ -1085,6 +1137,8 @@
1085
1137
  "draftValuesFromWire",
1086
1138
  "droppedFeatureSlugs",
1087
1139
  "emptyDraftValues",
1140
+ "engagementFor",
1141
+ "engagementIds",
1088
1142
  "envelopeFieldErrors",
1089
1143
  "explainListingsError",
1090
1144
  "failedResults",
@@ -1094,10 +1148,12 @@
1094
1148
  "featuresFromDaoList",
1095
1149
  "flowEndpoints",
1096
1150
  "isBatchValid",
1151
+ "isListingViewed",
1097
1152
  "isPubliclyVisible",
1098
1153
  "lifecycleCaption",
1099
1154
  "listingFieldErrors",
1100
1155
  "listingStatusView",
1156
+ "listingViewCount",
1101
1157
  "listingsErrorBundleEn",
1102
1158
  "listingsI18nBundleEn",
1103
1159
  "listingsQueryKeys",
@@ -1120,6 +1176,8 @@
1120
1176
  "useCreateDraft",
1121
1177
  "useDeleteListing",
1122
1178
  "useElevatableMandateGate",
1179
+ "useEngagedListing",
1180
+ "useEngagementOverlay",
1123
1181
  "useFavoriteListing",
1124
1182
  "useFavoriteToggle",
1125
1183
  "useFavorites",
@@ -1128,6 +1186,9 @@
1128
1186
  "useListingCards",
1129
1187
  "useListingComposer",
1130
1188
  "useListingDetail",
1189
+ "useListingEngagement",
1190
+ "useListingEngagementOverlay",
1191
+ "useListingEngagementScope",
1131
1192
  "useListingStatus",
1132
1193
  "useListingsAnalytics",
1133
1194
  "useListingsApi",
@@ -1138,7 +1199,8 @@
1138
1199
  "useMyListings",
1139
1200
  "usePublishListing",
1140
1201
  "useSaveDraft",
1141
- "useValidateDraft"
1202
+ "useValidateDraft",
1203
+ "withEngagement"
1142
1204
  ],
1143
1205
  "types": [
1144
1206
  "ComposeStage",
@@ -1165,6 +1227,11 @@
1165
1227
  "ListingDraftLimits",
1166
1228
  "ListingDraftPatch",
1167
1229
  "ListingDraftValues",
1230
+ "ListingEngagement",
1231
+ "ListingEngagementBag",
1232
+ "ListingEngagementBatch",
1233
+ "ListingEngagementFields",
1234
+ "ListingEngagementScopeProps",
1168
1235
  "ListingFeatureDao",
1169
1236
  "ListingFeatureDaoUnion",
1170
1237
  "ListingFeatureType",
@@ -1204,6 +1271,7 @@
1204
1271
  "UseFavoritesOptions",
1205
1272
  "UseListingComposerOptions",
1206
1273
  "UseListingDetailOptions",
1274
+ "UseListingEngagementOverlayOptions",
1207
1275
  "UseMyListingsOptions"
1208
1276
  ]
1209
1277
  }
package/nav-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@stapel/listings-react",
3
- "version": "0.18.2",
3
+ "version": "0.20.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "listings.detail",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/listings-react",
3
- "version": "0.18.2",
3
+ "version": "0.20.0",
4
4
  "description": "Headless React pair for stapel-listings: a typed client over the draft twin, the composer that turns a category schema into a submittable listing, and the owner's dashboard. Two independent axes are rendered as two — lifecycle `status` decides visibility and `moderation_status` decides nothing about it, so an edit to a live listing stays live and says 'under review' instead of vanishing. Feature values are drawn and mirrored by @stapel/attributes-react; photos arrive as an injected upload bag whose `refs` ARE `images_draft` and whose `settled` gates the submit; a publish refusal is routed onto the control that caused it by slug. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,14 +48,14 @@
48
48
  ],
49
49
  "size-limit": [
50
50
  {
51
- "name": "index — the headless pair (client + status axes + draft model + hooks): no antd. Raised 11 -> 12 KB for the elevation gate (useElevatableMandateGate + core's useElevation): the anonymous arm can now mint an identity for a named action instead of refusing, and that is a state hook the gate did not previously carry. Raised 11.5 -> 12 KB for envelopeFieldErrors and its API-field -> control table: a save-draft/create 400 names its field in the envelope rather than in a publish batch, and without the table the refusal had nowhere to land — which is how one over-precise coordinate painted two unreadable banners and left every control clean. Thirty-six bytes of routing table is what a legible refusal costs. Raised 12 -> 13 KB for two seams the mobile wave needed in the HEADLESS half: the category-feature copy source in model/features.ts (the option table a stored select does not carry, so a published listing prints \"Second-hand\" and not \"b-u\"), and firstUnsatisfied (the field a \"10 required details are still empty\" count actually points at, in the order the form asks). Both are model, not skin: a headless host renders its own controls and needs the same two answers",
51
+ "name": "index — the headless pair (client + status axes + draft model + hooks): no antd. Raised 11 -> 12 KB for the elevation gate (useElevatableMandateGate + core's useElevation): the anonymous arm can now mint an identity for a named action instead of refusing, and that is a state hook the gate did not previously carry. Raised 11.5 -> 12 KB for envelopeFieldErrors and its API-field -> control table: a save-draft/create 400 names its field in the envelope rather than in a publish batch, and without the table the refusal had nowhere to land — which is how one over-precise coordinate painted two unreadable banners and left every control clean. Thirty-six bytes of routing table is what a legible refusal costs. Raised 12 -> 13 KB for two seams the mobile wave needed in the HEADLESS half: the category-feature copy source in model/features.ts (the option table a stored select does not carry, so a published listing prints \"Second-hand\" and not \"b-u\"), and firstUnsatisfied (the field a \"10 required details are still empty\" count actually points at, in the order the form asks). Both are model, not skin: a headless host renders its own controls and needs the same two answers. Raised 13 -> 14 KB (measured 12.91) for the engagement overlay: a batch read, its normalized key, and the scope that runs it once for a page and hands every card its own entry. It is a whole new READ rather than a flag — and it is the read that makes the viewed/saved state visible at all on a feed and a SERP, which come from the search index and carry no per-viewer field. Landing at 12.91 of 13 would have been a budget passed by rounding",
52
52
  "path": "dist/index.js",
53
- "limit": "13 KB"
53
+ "limit": "14 KB"
54
54
  },
55
55
  {
56
- "name": "default — the antd skin (three cards, detail, composer, dashboard, the named slots and the delete confirmation) must stay out of the main bundle. Raised 17 -> 19 KB for the mobile wave: <ListingSerpCard> (photo strip + price-first block + action rail), <ListingFeedCard> and <FeedGrid> are two more card surfaces on the same shared target/favourite/photo parts, not three copies of one Raised 19 -> 20 KB for the desktop wave of the detail page: layout=\"split\" is a second ASSEMBLY of the money page (the sticky buy column, the two-column spec table, the widened measure) over the same parts, and GateReasonPopover is the blocked heart's third volume — the reason and the door disclosed on interaction, with the visually-hidden copy that keeps the refusal in the accessibility tree. Both are geometry and wiring, not new widgets",
56
+ "name": "default — the antd skin (three cards, detail, composer, dashboard, the named slots and the delete confirmation) must stay out of the main bundle. Raised 17 -> 19 KB for the mobile wave: <ListingSerpCard> (photo strip + price-first block + action rail), <ListingFeedCard> and <FeedGrid> are two more card surfaces on the same shared target/favourite/photo parts, not three copies of one Raised 19 -> 20 KB for the desktop wave of the detail page: layout=\"split\" is a second ASSEMBLY of the money page (the sticky buy column, the two-column spec table, the widened measure) over the same parts, and GateReasonPopover is the blocked heart's third volume — the reason and the door disclosed on interaction, with the visually-hidden copy that keeps the refusal in the accessibility tree. Both are geometry and wiring, not new widgets. Raised 20 -> 21 KB (measured 20.22) for the engagement wave, which is four small things and no new component: the disclosure became a controlled open/close so a pointer that hovers and then clicks does not shut the only explanation the control has; the heart draws a filled accent when saved and states a failed save through the shared ErrorAlert; an already-seen card dims through one rule in the sheet all three cards already hoist; and the listing page gained a view-count row. 216 bytes is what a control that reports its own outcome costs",
57
57
  "path": "dist/default/index.js",
58
- "limit": "20 KB"
58
+ "limit": "21 KB"
59
59
  },
60
60
  {
61
61
  "name": "i18n/ru — the Russian bundle is opt-in, not carried by the main entry",
@@ -102,13 +102,13 @@
102
102
  "size-limit": "^11.2.0",
103
103
  "typescript": "^5.8.3",
104
104
  "vitest": "^3.2.4",
105
- "@stapel/attributes-react": "^0.12.1",
106
- "@stapel/currencies-react": "^0.3.0",
105
+ "@stapel/attributes-react": "^0.14.1",
107
106
  "@stapel/core": "^0.23.1",
107
+ "@stapel/currencies-react": "^0.3.0",
108
108
  "@stapel/image": "^0.4.2",
109
109
  "@stapel/showcase": "^0.3.0",
110
110
  "@stapel/tokens": "^0.7.0",
111
- "@stapel/tokens-antd": "^0.15.0"
111
+ "@stapel/tokens-antd": "^0.16.0"
112
112
  },
113
113
  "engines": {
114
114
  "node": ">=22"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/listings-react",
4
- "version": "0.18.2",
4
+ "version": "0.20.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -45,15 +45,25 @@ export interface paths {
45
45
  cookie?: never;
46
46
  };
47
47
  /**
48
- * @description Listings CRUD plus owner lifecycle actions and favorites.
48
+ * @description Read one listing and count the open.
49
49
  *
50
- * Read and write live in one class, so the guest wall
51
- * (:func:`anonymous_write_refusal`, the ``ALLOW_ANONYMOUS_WRITES`` switch)
52
- * is applied per ACTION and never as a class permission: an anonymous
53
- * session must keep browsing and keep its favorites, and only the
54
- * authorship actions — ``create``, ``update``/``partial_update``,
55
- * ``save-draft``, ``publish`` are the ones that turn a caller into a
56
- * seller.
50
+ * A GET with a side effect, deliberately, because "opening a listing"
51
+ * IS the event and a storefront that had to POST it separately would
52
+ * report a different number than the page it drew (and would stop
53
+ * reporting at all the moment a client forgot the second call).
54
+ *
55
+ * The side effect is bounded on both ends. It cannot lie: the owner's
56
+ * own opens and unattributable ones are refused inside
57
+ * ``record_view``. And it cannot become a write per read: every repeat
58
+ * open inside ``VIEW_DEDUP_WINDOW_SECONDS`` is one cache hit and no
59
+ * query. The response is therefore safe to give a private cache and
60
+ * must never be given a shared one — which is the pre-existing rule
61
+ * for an authenticated read anyway.
62
+ *
63
+ * ``viewed`` is annotated BEFORE the recording, so the open that first
64
+ * sees a listing answers ``false`` and the next one answers ``true``.
65
+ * A card that greyed itself out on the very read that discovered it
66
+ * would be telling the reader they had already been here.
57
67
  *
58
68
  * **Permissions:** `IsAuthenticatedOrReadOnly`
59
69
  */
@@ -308,6 +318,32 @@ export interface paths {
308
318
  patch?: never;
309
319
  trace?: never;
310
320
  };
321
+ "/listings/api/v1/listings/engagement/": {
322
+ parameters: {
323
+ query?: never;
324
+ header?: never;
325
+ path?: never;
326
+ cookie?: never;
327
+ };
328
+ /**
329
+ * @description The per-viewer overlay for a page of cards, in one call.
330
+ *
331
+ * ``AllowAny`` on purpose: ``view_count`` is public, and an anonymous
332
+ * caller gets it with both per-viewer flags answering ``null``. A
333
+ * storefront therefore makes the same request whether or not anyone is
334
+ * signed in, and a guest's grid is not a second code path.
335
+ *
336
+ * **Permissions:** `AllowAny`
337
+ */
338
+ get: operations["listings_api_v1_listings_engagement_retrieve"];
339
+ put?: never;
340
+ post?: never;
341
+ delete?: never;
342
+ options?: never;
343
+ head?: never;
344
+ patch?: never;
345
+ trace?: never;
346
+ };
311
347
  "/listings/api/v1/listings/my/counters/": {
312
348
  parameters: {
313
349
  query?: never;
@@ -522,8 +558,8 @@ export interface components {
522
558
  favorited: boolean;
523
559
  listing_id: number;
524
560
  };
525
- FeatureDao: components["schemas"]["IntDao"] | components["schemas"]["FloatDao"] | components["schemas"]["StringDao"] | components["schemas"]["BoolDao"] | components["schemas"]["HexColorDao"] | components["schemas"]["SelectDao"] | components["schemas"]["DateDao"] | components["schemas"]["HeaderDao"] | components["schemas"]["HierarchicalSelectDao"] | components["schemas"]["ConvertibleUnitDao"] | components["schemas"]["RefSelectDao"] | components["schemas"]["RefHierarchicalSelectDao"] | components["schemas"]["GroupDao"];
526
- FeatureDto: components["schemas"]["IntDto"] | components["schemas"]["FloatDto"] | components["schemas"]["StringDto"] | components["schemas"]["BoolDto"] | components["schemas"]["HexColorDto"] | components["schemas"]["SelectDto"] | components["schemas"]["DateDto"] | components["schemas"]["HeaderDto"] | components["schemas"]["HierarchicalSelectDto"] | components["schemas"]["ConvertibleUnitDto"] | components["schemas"]["RefSelectDto"] | components["schemas"]["RefHierarchicalSelectDto"] | components["schemas"]["GroupDto"];
561
+ FeatureDao: components["schemas"]["RefSelectDao"] | components["schemas"]["IntDao"] | components["schemas"]["FloatDao"] | components["schemas"]["StringDao"] | components["schemas"]["BoolDao"] | components["schemas"]["HexColorDao"] | components["schemas"]["SelectDao"] | components["schemas"]["DateDao"] | components["schemas"]["HeaderDao"] | components["schemas"]["HierarchicalSelectDao"] | components["schemas"]["ConvertibleUnitDao"] | components["schemas"]["RefHierarchicalSelectDao"] | components["schemas"]["GroupDao"];
562
+ FeatureDto: components["schemas"]["RefSelectDto"] | components["schemas"]["IntDto"] | components["schemas"]["FloatDto"] | components["schemas"]["StringDto"] | components["schemas"]["BoolDto"] | components["schemas"]["HexColorDto"] | components["schemas"]["SelectDto"] | components["schemas"]["DateDto"] | components["schemas"]["HeaderDto"] | components["schemas"]["HierarchicalSelectDto"] | components["schemas"]["ConvertibleUnitDto"] | components["schemas"]["RefHierarchicalSelectDto"] | components["schemas"]["GroupDto"];
527
563
  /** @description Serializer for FeatureValidationResult. */
528
564
  FeatureValidationResult: {
529
565
  id?: unknown;
@@ -738,7 +774,7 @@ export interface components {
738
774
  readonly id: number;
739
775
  title?: string;
740
776
  /** Format: decimal */
741
- price?: string;
777
+ price?: string | null;
742
778
  /** Format: decimal */
743
779
  price_base?: string | null;
744
780
  currency?: string;
@@ -784,6 +820,8 @@ export interface components {
784
820
  stock_quantity?: number | null;
785
821
  status?: components["schemas"]["StatusD41Enum"];
786
822
  readonly is_favorited: boolean | null;
823
+ readonly viewed: boolean | null;
824
+ readonly view_count: number;
787
825
  };
788
826
  /** @description Full listing detail. */
789
827
  ListingDetail: {
@@ -795,7 +833,7 @@ export interface components {
795
833
  description?: string;
796
834
  language?: string;
797
835
  /** Format: decimal */
798
- price?: string;
836
+ price?: string | null;
799
837
  /** Format: decimal */
800
838
  price_base?: string | null;
801
839
  currency?: string;
@@ -868,6 +906,8 @@ export interface components {
868
906
  /** Format: date-time */
869
907
  readonly updated_at: string;
870
908
  readonly is_favorited: boolean | null;
909
+ readonly viewed: boolean | null;
910
+ readonly view_count: number;
871
911
  };
872
912
  /**
873
913
  * @description Create/update the draft twin fields.
@@ -907,6 +947,30 @@ export interface components {
907
947
  /** Format: date-time */
908
948
  readonly updated_at: string;
909
949
  };
950
+ /**
951
+ * @description The per-viewer overlay for ONE listing in a batch read.
952
+ *
953
+ * Exists because a storefront's grid does not come from this module: the
954
+ * SERP is served by the search index, whose stored card can carry neither a
955
+ * flag that differs per reader nor a counter that moves faster than a
956
+ * document re-indexed on a listing event. So the grid draws the card from
957
+ * search and asks HERE, once for the whole page, for the three things that
958
+ * are about the person looking.
959
+ */
960
+ ListingEngagement: {
961
+ /** @description Distinct viewers who have opened this listing. Public — it is the same number for every reader. */
962
+ view_count: number;
963
+ /** @description Whether the CALLER has opened this listing before. `null` for an anonymous caller: nothing is remembered for a stranger, and `false` would be a claim rather than an absence. */
964
+ viewed: boolean | null;
965
+ /** @description Whether the CALLER has favorited it. `null` for anonymous, same reason. */
966
+ is_favorited: boolean | null;
967
+ };
968
+ /** @description ``{listing id: overlay}``. An id with no listing is simply absent. */
969
+ ListingEngagementBatch: {
970
+ items: {
971
+ [key: string]: components["schemas"]["ListingEngagement"];
972
+ };
973
+ };
910
974
  /** @description Lightweight status view (mirrors the listings.status comm Function). */
911
975
  ListingStatus: {
912
976
  status: components["schemas"]["StatusD41Enum"];
@@ -954,7 +1018,7 @@ export interface components {
954
1018
  readonly id: number;
955
1019
  title?: string;
956
1020
  /** Format: decimal */
957
- price?: string;
1021
+ price?: string | null;
958
1022
  /** Format: decimal */
959
1023
  price_base?: string | null;
960
1024
  currency?: string;
@@ -1000,6 +1064,8 @@ export interface components {
1000
1064
  stock_quantity?: number | null;
1001
1065
  status?: components["schemas"]["StatusD41Enum"];
1002
1066
  readonly is_favorited: boolean | null;
1067
+ readonly viewed: boolean | null;
1068
+ readonly view_count: number;
1003
1069
  moderation_status?: components["schemas"]["ModerationStatusEnum"];
1004
1070
  title_draft?: string;
1005
1071
  /** Format: decimal */
@@ -1641,6 +1707,28 @@ export interface operations {
1641
1707
  };
1642
1708
  };
1643
1709
  };
1710
+ listings_api_v1_listings_engagement_retrieve: {
1711
+ parameters: {
1712
+ query: {
1713
+ /** @description Listing ids, comma-separated or the parameter repeated. Capped at ENGAGEMENT_BATCH_LIMIT ids per call — one page of cards, not a crawl of the board. */
1714
+ ids: string[];
1715
+ };
1716
+ header?: never;
1717
+ path?: never;
1718
+ cookie?: never;
1719
+ };
1720
+ requestBody?: never;
1721
+ responses: {
1722
+ 200: {
1723
+ headers: {
1724
+ [name: string]: unknown;
1725
+ };
1726
+ content: {
1727
+ "application/json": components["schemas"]["ListingEngagementBatch"];
1728
+ };
1729
+ };
1730
+ };
1731
+ };
1644
1732
  listings_api_v1_listings_my_counters_retrieve: {
1645
1733
  parameters: {
1646
1734
  query?: never;