@stapel/search-react 0.11.0 → 0.12.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 (111) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/MODULE.md +111 -0
  3. package/README.md +42 -0
  4. package/dist/api/generated/schema.d.ts +60 -6
  5. package/dist/api/generated/schema.d.ts.map +1 -1
  6. package/dist/api/searchApi.d.ts +13 -6
  7. package/dist/api/searchApi.d.ts.map +1 -1
  8. package/dist/api/searchApi.js.map +1 -1
  9. package/dist/api/types.d.ts +107 -1
  10. package/dist/api/types.d.ts.map +1 -1
  11. package/dist/api/types.js +29 -0
  12. package/dist/api/types.js.map +1 -1
  13. package/dist/default/FacetPanelPane.d.ts +13 -2
  14. package/dist/default/FacetPanelPane.d.ts.map +1 -1
  15. package/dist/default/FacetPanelPane.js +3 -1
  16. package/dist/default/FacetPanelPane.js.map +1 -1
  17. package/dist/default/FilterChips.d.ts +88 -3
  18. package/dist/default/FilterChips.d.ts.map +1 -1
  19. package/dist/default/FilterChips.js +161 -13
  20. package/dist/default/FilterChips.js.map +1 -1
  21. package/dist/default/LocationSummaryLine.d.ts.map +1 -1
  22. package/dist/default/LocationSummaryLine.js +1 -1
  23. package/dist/default/LocationSummaryLine.js.map +1 -1
  24. package/dist/default/SearchBox.d.ts.map +1 -1
  25. package/dist/default/SearchBox.js +74 -4
  26. package/dist/default/SearchBox.js.map +1 -1
  27. package/dist/default/SearchPage.d.ts +29 -1
  28. package/dist/default/SearchPage.d.ts.map +1 -1
  29. package/dist/default/SearchPage.js +25 -10
  30. package/dist/default/SearchPage.js.map +1 -1
  31. package/dist/default/SearchResultsPane.d.ts +22 -0
  32. package/dist/default/SearchResultsPane.d.ts.map +1 -1
  33. package/dist/default/SearchResultsPane.js +2 -2
  34. package/dist/default/SearchResultsPane.js.map +1 -1
  35. package/dist/default/SortSelect.d.ts +23 -0
  36. package/dist/default/SortSelect.d.ts.map +1 -1
  37. package/dist/default/SortSelect.js +29 -9
  38. package/dist/default/SortSelect.js.map +1 -1
  39. package/dist/default/index.d.ts +2 -2
  40. package/dist/default/index.d.ts.map +1 -1
  41. package/dist/default/index.js +1 -1
  42. package/dist/default/index.js.map +1 -1
  43. package/dist/headless/FacetPanel.d.ts +10 -0
  44. package/dist/headless/FacetPanel.d.ts.map +1 -1
  45. package/dist/headless/FacetPanel.js +9 -1
  46. package/dist/headless/FacetPanel.js.map +1 -1
  47. package/dist/headless/useFacetLabels.d.ts +39 -0
  48. package/dist/headless/useFacetLabels.d.ts.map +1 -0
  49. package/dist/headless/useFacetLabels.js +129 -0
  50. package/dist/headless/useFacetLabels.js.map +1 -0
  51. package/dist/headless/useSearchBox.d.ts +71 -0
  52. package/dist/headless/useSearchBox.d.ts.map +1 -1
  53. package/dist/headless/useSearchBox.js +67 -2
  54. package/dist/headless/useSearchBox.js.map +1 -1
  55. package/dist/i18n/es.d.ts.map +1 -1
  56. package/dist/i18n/es.js +7 -0
  57. package/dist/i18n/es.js.map +1 -1
  58. package/dist/i18n/keys.d.ts +45 -0
  59. package/dist/i18n/keys.d.ts.map +1 -1
  60. package/dist/i18n/keys.js +54 -0
  61. package/dist/i18n/keys.js.map +1 -1
  62. package/dist/i18n/ru.d.ts.map +1 -1
  63. package/dist/i18n/ru.js +9 -0
  64. package/dist/i18n/ru.js.map +1 -1
  65. package/dist/index.d.ts +6 -4
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4 -3
  68. package/dist/index.js.map +1 -1
  69. package/dist/model/queries.d.ts +6 -5
  70. package/dist/model/queries.d.ts.map +1 -1
  71. package/dist/model/queries.js +4 -3
  72. package/dist/model/queries.js.map +1 -1
  73. package/dist/model/queryKeys.d.ts +12 -0
  74. package/dist/model/queryKeys.d.ts.map +1 -1
  75. package/dist/model/queryKeys.js +7 -0
  76. package/dist/model/queryKeys.js.map +1 -1
  77. package/dist/nav/manifest.d.ts +21 -0
  78. package/dist/nav/manifest.d.ts.map +1 -1
  79. package/dist/nav/manifest.js +3 -2
  80. package/dist/nav/manifest.js.map +1 -1
  81. package/dist/state/facets.d.ts +56 -15
  82. package/dist/state/facets.d.ts.map +1 -1
  83. package/dist/state/facets.js +101 -27
  84. package/dist/state/facets.js.map +1 -1
  85. package/llms.txt +2 -2
  86. package/manifest.json +22 -2
  87. package/nav-manifest.json +4 -3
  88. package/package.json +11 -11
  89. package/src/analytics/generated/events.json +1 -1
  90. package/src/api/generated/schema.ts +60 -6
  91. package/src/api/searchApi.ts +13 -6
  92. package/src/api/types.ts +115 -1
  93. package/src/default/FacetPanelPane.tsx +16 -2
  94. package/src/default/FilterChips.tsx +285 -38
  95. package/src/default/LocationSummaryLine.tsx +6 -1
  96. package/src/default/SearchBox.tsx +134 -6
  97. package/src/default/SearchPage.tsx +79 -5
  98. package/src/default/SearchResultsPane.tsx +48 -11
  99. package/src/default/SortSelect.tsx +70 -9
  100. package/src/default/index.ts +10 -2
  101. package/src/headless/FacetPanel.tsx +20 -1
  102. package/src/headless/useFacetLabels.ts +172 -0
  103. package/src/headless/useSearchBox.ts +132 -5
  104. package/src/i18n/es.ts +8 -0
  105. package/src/i18n/keys.ts +56 -0
  106. package/src/i18n/ru.ts +10 -0
  107. package/src/index.ts +21 -3
  108. package/src/model/queries.ts +6 -5
  109. package/src/model/queryKeys.ts +23 -0
  110. package/src/nav/manifest.ts +24 -2
  111. package/src/state/facets.ts +123 -39
@@ -8,27 +8,86 @@
8
8
  * they would have if you swapped to them. A panel that greys the siblings
9
9
  * out has silently converted a drill-down facet into a naive one, and the
10
10
  * e2e leg in the spec (§7.2) exists to catch exactly that.
11
- * 2. **Option LABELS come from two places, in that order.** The category's
12
- * feature schema`categoryFeatures`, the second slot-seam of the pair,
13
- * filled by the container from `categories-react` (spec §6.2 item 2) —
14
- * and, under it, the answer's own `facet_labels` (stapel-search 0.4.0+).
15
- *
16
- * Until 0.4.0 the server sent `{value: count}` and nothing else, on the
17
- * reasoning that the container has the schema anyway. That reasoning had
18
- * a hole the size of a marketplace: the slot is OPTIONAL, a live
19
- * classified board never filled it, and its buyers read "Condition:
20
- * **b-u**", "Listing kind: **prodayu-svoe**", "Screen condition:
21
- * **bez-defektov**" on the SERP and in the filter chips. The schema still
22
- * wins where it resolves — the client fetched it with its own
23
- * `Accept-Language` — and the answer's captions are the floor beneath it.
24
- * Neither invents a label: a value nobody names renders as itself.
11
+ * 2. **Option LABELS have one stated order: ANSWER, then schema, then the
12
+ * raw value**with a host resolver between the last two, applied by
13
+ * `useHostFacetLabels` on what this module could not name.
14
+ *
15
+ * The answer leads because it is the only source that always exists and
16
+ * the only one that has RESOLVED anything: `categoryFeatures` is an
17
+ * optional slot a live classified board never filled, and even where it is
18
+ * filled a `ref_select` config carries a pointer to a vocabulary and no
19
+ * option table at all so the schema cannot name `apple` or `chernyy` no
20
+ * matter who threads it through. `facet_labels` (stapel-search 0.4.0+,
21
+ * vocabulary-backed from 0.6.0) is the server reading that vocabulary
22
+ * against the snapshot the documents were written with, and answering in
23
+ * the request's own language. The schema is the floor under it, for a
24
+ * server too old to send captions and for a slug the answer omits.
25
+ *
26
+ * Neither invents a label: a value nobody names renders as itself. A page
27
+ * against a pre-0.4.0 server therefore behaves exactly as it did — the key
28
+ * is absent, every option falls through to the schema, and nothing
29
+ * crashes on the missing key.
25
30
  *
26
31
  * A slug the server SKIPPED (`facet_meta.skipped`, dropped at
27
32
  * `MAX_FACET_FIELDS`) is not counted at all. Its options carry `count: null`,
28
33
  * never `0` — "we did not count this" and "there are none" are different
29
34
  * sentences and the honest one has to survive to the screen.
35
+ *
36
+ * 3. **Not every counted slug is a FILTER.** The plan is built from the leaf
37
+ * category's feature defs and the counter counts whatever is indexed, so a
38
+ * live classified deployment answers with `imei: {"355971829187494": 1}`
39
+ * and `video_file_url: {}` beside its brand and its condition. Neither is
40
+ * something a person narrows by — one is unique per document, the other is
41
+ * a URL — and on a 390px chip row a chip offering one IMEI with a count of
42
+ * one pushes the chips that DO narrow off the screen. Which slugs can be
43
+ * chipped is therefore decided from the category's own feature defs, by
44
+ * value TYPE (see {@link FACETABLE_FEATURE_TYPES}), not by what came back.
45
+ */
46
+ import { VOCABULARY_BACKED_TYPES, featureConfig, featureName, featureType, formatFeatureValue, } from "@stapel/attributes-react";
47
+ /**
48
+ * Value types whose values are a BOUNDED OPTION SET — the only kind of
49
+ * feature a person can be offered as a filter.
50
+ *
51
+ * The list is not invented here: the ref/vocabulary-backed half is
52
+ * `@stapel/attributes-react`'s own `VOCABULARY_BACKED_TYPES` (the one place
53
+ * that knows which types are drawn from a vocabulary), and the rest are the
54
+ * select family plus `bool`. Everything else a category can declare — a
55
+ * `string`, an `int`, a `date`, a `hex_color`, a `group` — enumerates as many
56
+ * terms as there are documents, which is a list, not a choice.
57
+ *
58
+ * `int`/`float`/`convertible_unit` are absent here and present in
59
+ * `RANGE_FEATURE_TYPES` (`state/ranges.ts`): a number is narrowed with two
60
+ * bounds, not with a checkbox per value. They are not dropped from the panel,
61
+ * they are drawn by the other half of the model.
30
62
  */
31
- import { featureConfig, featureName, featureType, formatFeatureValue } from "@stapel/attributes-react";
63
+ export const FACETABLE_FEATURE_TYPES = [
64
+ "bool",
65
+ "hierarchical_select",
66
+ ...VOCABULARY_BACKED_TYPES,
67
+ "select",
68
+ ];
69
+ /**
70
+ * Can a person filter by the slug this feature def describes?
71
+ *
72
+ * **A missing feature def is not a "no".** `categoryFeatures` is an OPTIONAL
73
+ * slot and a whole deployment can run without it; a feature can also be
74
+ * retired between the write that indexed a value and the read that counts it.
75
+ * Answering "not facetable" for an absent def would blank the entire chip row
76
+ * for every host that never threaded the schema through — the same empty row
77
+ * this rule exists to fix, arrived at from the other side. So the schema can
78
+ * only ever REMOVE a chip it names and disowns; silence removes nothing.
79
+ *
80
+ * The same reasoning covers a def with no `config.type` at all: an untyped
81
+ * feature is a def that says nothing, and nothing is not a verdict.
82
+ */
83
+ export function isFacetableFeature(feature) {
84
+ if (feature === undefined)
85
+ return true;
86
+ const type = featureType(feature);
87
+ if (type === undefined)
88
+ return true;
89
+ return FACETABLE_FEATURE_TYPES.includes(type);
90
+ }
32
91
  function translate(t, key) {
33
92
  if (t === undefined)
34
93
  return key;
@@ -78,20 +137,23 @@ function declaredOptionValues(feature) {
78
137
  return out;
79
138
  }
80
139
  /**
81
- * One option's caption: schema, then answer, then the raw value.
82
- *
83
- * Order matters and is deliberate. Both sources are the same category
84
- * config, but the client fetched its copy with its own `Accept-Language`, so
85
- * where the host threaded a schema through and it names the value, that is
86
- * the better-localized answer. Where it does not no schema, a slug the
87
- * schema omits, a value added since the server's caption is what stops
88
- * `b-u` reaching a buyer.
140
+ * One option's caption: the ANSWER, then the schema, then the raw value.
141
+ *
142
+ * The answer leads because it is the source that always exists and the only
143
+ * one that can name a vocabulary-backed value at all: a `ref_select`'s config
144
+ * is a POINTER (`optionsRef`), so a host who threaded the whole schema through
145
+ * still has nothing to print for `apple`. The server read that vocabulary
146
+ * against the snapshot the documents carry and answered in the request's own
147
+ * language, which is a strictly better-informed caption than the option table
148
+ * the schema may or may not hold.
149
+ *
150
+ * The schema is the floor: a server too old to send `facet_labels`, a slug the
151
+ * answer omits, a value the vocabulary has since dropped. Below both, the raw
152
+ * value — never a blank, and never a guess.
89
153
  */
90
154
  function resolveLabel(input, feature, slug, value, labelOptions) {
91
- const viaSchema = facetOptionLabel(feature, value, labelOptions);
92
- if (feature !== undefined && viaSchema !== value)
93
- return viaSchema;
94
- return serverLabel(input.facetLabels, slug, value, input.t) ?? viaSchema;
155
+ return (serverLabel(input.facetLabels, slug, value, input.t) ??
156
+ facetOptionLabel(feature, value, labelOptions));
95
157
  }
96
158
  /**
97
159
  * A facet value as a person reads it, through
@@ -145,6 +207,13 @@ export function facetOptionLabel(feature, value, options) {
145
207
  * person has filtered on — the last one matters: a filter whose slug fell out
146
208
  * of the plan must stay visible, or it becomes a constraint with no control to
147
209
  * remove it.
210
+ *
211
+ * A slug the category schema names and types as something no one can choose
212
+ * from (see {@link isFacetableFeature}) produces no group at all — not an
213
+ * empty one, because an empty group is still a heading in the panel and still
214
+ * a chip in the row. The APPLIED-filter clause outranks the type rule: a
215
+ * constraint the URL carries always gets its control back, whatever the schema
216
+ * now says about it, or a person is left holding a filter they cannot clear.
148
217
  */
149
218
  export function buildFacetGroups(input) {
150
219
  const bySlug = new Map();
@@ -161,6 +230,11 @@ export function buildFacetGroups(input) {
161
230
  if (seen.has(slug))
162
231
  continue;
163
232
  seen.add(slug);
233
+ // Applied first, type second — in that order, so an `imei` somebody
234
+ // somehow got into a link keeps the control that removes it.
235
+ const applied = (input.state.filters[slug] ?? []).length > 0;
236
+ if (!applied && !isFacetableFeature(bySlug.get(slug)))
237
+ continue;
164
238
  slugs.push(slug);
165
239
  }
166
240
  return slugs.map((slug) => {
@@ -1 +1 @@
1
- {"version":3,"file":"facets.js","sourceRoot":"","sources":["../../src/state/facets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"facets.js","sourceRoot":"","sources":["../../src/state/facets.ts"],"names":[],"mappings":"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"}
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
- # @stapel/search-react 0.11.0
1
+ # @stapel/search-react 0.12.0
2
2
 
3
- Headless React flow pair for stapel-search (contract >=0.4 <0.5) — business + state, zero visual opinion.
3
+ Headless React flow pair for stapel-search (contract >=0.7 <0.8) — 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
 
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/search-react",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "backend": {
6
6
  "module": "stapel-search",
7
- "contract": ">=0.4 <0.5"
7
+ "contract": ">=0.7 <0.8"
8
8
  },
9
9
  "layers": [
10
10
  "api",
@@ -703,6 +703,10 @@
703
703
  "error.500.internal",
704
704
  "error.503.mandate_unavailable",
705
705
  "error.503.search_backend_unavailable",
706
+ "search.box.categories",
707
+ "search.box.category_count",
708
+ "search.box.category_count.one",
709
+ "search.box.category_count.other",
706
710
  "search.box.clear",
707
711
  "search.box.label",
708
712
  "search.box.placeholder",
@@ -745,6 +749,7 @@
745
749
  "search.filters.chips_label",
746
750
  "search.filters.dismiss",
747
751
  "search.filters.open",
752
+ "search.filters.short",
748
753
  "search.filters.show_count",
749
754
  "search.filters.show_count.one",
750
755
  "search.filters.show_count.other",
@@ -763,6 +768,9 @@
763
768
  "search.limit.from_link",
764
769
  "search.limit.label",
765
770
  "search.limit.option",
771
+ "search.nav.ranking",
772
+ "search.nav.ranking.short",
773
+ "search.nav.results",
766
774
  "search.range.price",
767
775
  "search.ranking.applies_to",
768
776
  "search.ranking.empty",
@@ -819,6 +827,7 @@
819
827
  ],
820
828
  "exports": {
821
829
  "runtime": [
830
+ "FACETABLE_FEATURE_TYPES",
822
831
  "FILTER_PREFIX",
823
832
  "FacetPanel",
824
833
  "RANGE_FEATURE_TYPES",
@@ -839,6 +848,8 @@
839
848
  "SEARCH_QUERY_MAX_CHARS",
840
849
  "SEARCH_SORTS",
841
850
  "SEARCH_WINDOW_EXCEEDED",
851
+ "SUGGEST_DEGRADED_CATEGORIES",
852
+ "SUGGEST_DEGRADED_ROLLUP",
842
853
  "SUGGEST_MAX_LIMIT",
843
854
  "SUGGEST_MIN_CHARS",
844
855
  "SearchProvider",
@@ -860,9 +871,11 @@
860
871
  "explainSearchError",
861
872
  "facetOptionLabel",
862
873
  "isCountNuanceOnly",
874
+ "isFacetableFeature",
863
875
  "isRangeFeature",
864
876
  "isRangeUsable",
865
877
  "navEntries",
878
+ "offerableCategories",
866
879
  "ownsParam",
867
880
  "parseDegradations",
868
881
  "parseSearchState",
@@ -875,9 +888,11 @@
875
888
  "searchQueryParams",
876
889
  "setFilterValues",
877
890
  "setRangeValue",
891
+ "suggestTerms",
878
892
  "toggleFilterValue",
879
893
  "useAppliedSort",
880
894
  "useFacetPanel",
895
+ "useHostFacetLabels",
881
896
  "useRankingDisclosure",
882
897
  "useSearchAnalytics",
883
898
  "useSearchApi",
@@ -894,6 +909,8 @@
894
909
  "BuildRangeGroupsInput",
895
910
  "CreateSearchRuntimeOptions",
896
911
  "FacetGroup",
912
+ "FacetLabelRequest",
913
+ "FacetLabelResolver",
897
914
  "FacetMeta",
898
915
  "FacetOption",
899
916
  "FacetPanelBag",
@@ -934,6 +951,9 @@
934
951
  "SearchStateIssueCode",
935
952
  "SearchStatePatch",
936
953
  "SearchStateProviderProps",
954
+ "SuggestAnswer",
955
+ "SuggestCategory",
956
+ "SuggestCategoryMatch",
937
957
  "SuggestParams",
938
958
  "SuggestResponse",
939
959
  "UseSearchBoxOptions",
package/nav-manifest.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "package": "@stapel/search-react",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "search.results",
7
- "labelKey": "search.results.title",
7
+ "labelKey": "search.nav.results",
8
8
  "icon": "SearchOutlined",
9
9
  "route": {
10
10
  "path": "/s"
@@ -23,7 +23,8 @@
23
23
  },
24
24
  {
25
25
  "id": "search.ranking",
26
- "labelKey": "search.ranking.title",
26
+ "labelKey": "search.nav.ranking",
27
+ "shortLabelKey": "search.nav.ranking.short",
27
28
  "icon": "OrderedListOutlined",
28
29
  "route": {
29
30
  "path": "/ranking-disclosure"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/search-react",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Headless React pair for stapel-search: a typed query client, TanStack Query hooks, and a URL-first state codec that makes a search shareable by construction (filters, ranges, geo, sort and the keyset cursor all live in the query string). Drill-down facets rendered with their remaining counts and with the server's own honesty flags — approximate, skipped, degraded — never swallowed; keyset pagination with the window refusal named; DSA Art. 26 `promoted` marking carried into every card slot and the P2B Art. 5 ranking disclosure exposed as data. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin, and /router binds the codec to react-router's useSearchParams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -52,14 +52,14 @@
52
52
  ],
53
53
  "size-limit": [
54
54
  {
55
- "name": "index — the headless pair (client + hooks + URL codec + facet model): no antd, no react-router",
55
+ "name": "index — the headless pair (client + hooks + URL codec + facet model incl. the facetability rule and the host label seam): no antd, no react-router",
56
56
  "path": "dist/index.js",
57
- "limit": "10 KB"
57
+ "limit": "10.5 KB"
58
58
  },
59
59
  {
60
- "name": "default — the antd skin (query box + typeahead, filters incl. ranges/geo/category slots + the phone chip row and the location summary row, results incl. the view switch, ranking) must stay out of the main bundle",
60
+ "name": "default — the antd skin (query box + a typeahead that offers CATEGORY destinations with their live counts, filters incl. ranges/geo/category slots + the phone chip row with its leading category chip and the location summary row, results incl. the view switch, ranking) must stay out of the main bundle",
61
61
  "path": "dist/default/index.js",
62
- "limit": "18 KB"
62
+ "limit": "19.5 KB"
63
63
  },
64
64
  {
65
65
  "name": "router — the react-router binding is opt-in; the main entry must never pull a router",
@@ -78,10 +78,10 @@
78
78
  }
79
79
  ],
80
80
  "peerDependencies": {
81
- "@stapel/attributes-react": ">=0.1.0",
81
+ "@stapel/attributes-react": ">=0.4.0",
82
82
  "@stapel/core": ">=0.18.1 <1.0.0",
83
83
  "@stapel/image": ">=0.3.0",
84
- "@stapel/tokens-antd": ">=0.7.0",
84
+ "@stapel/tokens-antd": ">=0.11.0",
85
85
  "@tanstack/react-query": "^5.0.0",
86
86
  "antd": ">=5.20.0 <7",
87
87
  "react": ">=19",
@@ -115,12 +115,12 @@
115
115
  "size-limit": "^11.2.0",
116
116
  "typescript": "^5.8.3",
117
117
  "vitest": "^3.2.4",
118
- "@stapel/attributes-react": "^0.5.0",
119
- "@stapel/core": "^0.21.0",
118
+ "@stapel/attributes-react": "^0.6.0",
119
+ "@stapel/core": "^0.22.0",
120
120
  "@stapel/image": "^0.4.2",
121
+ "@stapel/showcase": "^0.3.0",
121
122
  "@stapel/tokens": "^0.6.0",
122
- "@stapel/tokens-antd": "^0.10.0",
123
- "@stapel/showcase": "^0.3.0"
123
+ "@stapel/tokens-antd": "^0.11.0"
124
124
  },
125
125
  "engines": {
126
126
  "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/search-react",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -102,13 +102,24 @@ export interface paths {
102
102
  cookie?: never;
103
103
  };
104
104
  /**
105
- * Title prefixes from the index
106
- * @description ``GET /search/api/v1/suggest`` — title prefixes out of the index.
105
+ * Type-ahead: category paths with live counts, plus title prefixes
106
+ * @description ``GET /search/api/v1/suggest`` — what to offer under the search box.
107
107
  *
108
- * Not out of a query log: no query log is kept, which is a privacy
108
+ * ``categories`` is the primary half: each row is a destination with its
109
+ * full ancestor path and the number of listings a buyer would actually
110
+ * see there, ranked by that number. ``terms`` is the title-prefix half.
111
+ *
112
+ * Neither comes from a query log: no query log is kept, which is a privacy
109
113
  * decision before it is a product one, and on day one there would be
110
114
  * nothing in it anyway.
111
115
  *
116
+ * The answer is public, identical for every reader and requested on every
117
+ * keystroke, so it carries ``Cache-Control: public`` and an ``ETag``. This
118
+ * is the module's first conditional read — ``query`` has none, because a
119
+ * SERP answer embeds ``took_ms`` and a cursor and would revalidate to a
120
+ * miss every time. Here the payload is deliberately free of anything that
121
+ * varies with the clock.
122
+ *
112
123
  * **Permissions:** `AllowAny`
113
124
  */
114
125
  get: operations["search_api_v1_suggest_retrieve"];
@@ -124,6 +135,30 @@ export interface paths {
124
135
  export type webhooks = Record<string, never>;
125
136
  export interface components {
126
137
  schemas: {
138
+ /** @description One destination in the dropdown, ready to render and ready to follow. */
139
+ CategorySuggestion: {
140
+ /** @description Category id. */
141
+ id: number;
142
+ /** @description Category slug. */
143
+ slug: string;
144
+ /** @description The category's own display name. */
145
+ name: string;
146
+ /** @description Display names root->leaf, e.g. ['Мужская одежда', 'Шорты']. This is what distinguishes three categories that share a name. */
147
+ path: string[];
148
+ /** @description The ancestry as ids joined with '/'. Pass it verbatim as the `category` parameter of /query — do not re-join path segments yourself. */
149
+ category: string;
150
+ /** @description Live listings a buyer would see under this category, descendants included — the same number the SERP reports for it. */
151
+ count: number;
152
+ /** @description Number of segments in `path`. */
153
+ depth: number;
154
+ /**
155
+ * @description How the name matched. Informational; ranking is by `count`.
156
+ *
157
+ * * `prefix` - prefix
158
+ * * `substring` - substring
159
+ */
160
+ match: components["schemas"]["MatchEnum"];
161
+ };
127
162
  /** @description Captions for one slug's option codes. */
128
163
  FacetLabels: {
129
164
  /** @description True when `values` holds translation KEYS to run through the catalogue; false when it holds literal captions. The reader cannot tell by looking — `b.apple` and `Б/у` are both strings. */
@@ -155,6 +190,12 @@ export interface components {
155
190
  lag_seconds?: number | null;
156
191
  stale_reason?: string;
157
192
  };
193
+ /**
194
+ * @description * `prefix` - prefix
195
+ * * `substring` - substring
196
+ * @enum {string}
197
+ */
198
+ MatchEnum: "prefix" | "substring";
158
199
  /** @description The P2B Art. 5 disclosure, generated from the scorer registry. */
159
200
  RankingResponse: {
160
201
  doc_type: string;
@@ -242,7 +283,16 @@ export interface components {
242
283
  took_ms: number;
243
284
  };
244
285
  SuggestResponse: {
286
+ /** @description Destinations, ranked by live listing count desc, then depth, then name. */
287
+ categories: components["schemas"]["CategorySuggestion"][];
288
+ /** @description Title prefixes from the index. */
289
+ terms: string[];
290
+ /** @description Deprecated alias of `terms`, kept for one minor. */
245
291
  items: string[];
292
+ /** @description Which dictionary answered — the same resolution /query reports. */
293
+ language: string;
294
+ /** @description What this answer could not do: `category_suggestions` (no provider for category names), `category_rollup` (no ancestry, so counts would read 0). */
295
+ degraded: string[];
246
296
  backend: string;
247
297
  };
248
298
  };
@@ -372,11 +422,15 @@ export interface operations {
372
422
  };
373
423
  search_api_v1_suggest_retrieve: {
374
424
  parameters: {
375
- query: {
425
+ query?: {
426
+ /** @description Language of the query: picks the dictionary, so «shorty» reaches «шорты». Falls back to Accept-Language, then DEFAULT_LANGUAGE. */
427
+ lang?: string;
428
+ /** @description Rows per half. Capped by MAX_SUGGEST_LIMIT. */
376
429
  limit?: number;
377
- /** @description Title prefix. */
430
+ /** @description What the buyer has typed so far. */
378
431
  q?: string;
379
- type: string;
432
+ /** @description Registered doc_type. Optional when exactly one type is registered — a type-ahead should not have to name the only corpus there is. */
433
+ type?: string;
380
434
  };
381
435
  header?: never;
382
436
  path?: never;
@@ -3,8 +3,8 @@ import type {
3
3
  RankingResponse,
4
4
  SearchQueryState,
5
5
  SearchResponse,
6
+ SuggestAnswer,
6
7
  SuggestParams,
7
- SuggestResponse,
8
8
  } from "./types.js";
9
9
 
10
10
  /**
@@ -53,15 +53,22 @@ export interface SearchApi {
53
53
  ): Promise<SearchResponse>;
54
54
 
55
55
  /**
56
- * Title-prefix suggestions. Typed and callable, but this pair ships NO hook
57
- * and no autocomplete widget over it (spec §4.2 defers the debounce +
58
- * keyboard work). A host that wants one today calls this directly rather
59
- * than waiting for a version of the pair that has it.
56
+ * What to offer under the search box: CATEGORIES first, then title-prefix
57
+ * terms (stapel-search 0.7.0).
58
+ *
59
+ * Neither half comes from a query log the module keeps none, which is a
60
+ * privacy decision before it is a product one — so every suggestion is a
61
+ * destination or a search that has results.
62
+ *
63
+ * Typed as {@link SuggestAnswer} rather than as the generated
64
+ * `SuggestResponse`: a build regenerated against a pre-0.7.0 schema would
65
+ * otherwise hide the categories half from the compiler. See that type for
66
+ * why every member but `backend` is optional.
60
67
  */
61
68
  suggest(
62
69
  params: SuggestParams,
63
70
  options?: { readonly signal?: AbortSignal }
64
- ): Promise<SuggestResponse>;
71
+ ): Promise<SuggestAnswer>;
65
72
 
66
73
  /**
67
74
  * The P2B Art. 5 ranking disclosure for a doc type: which parameters rank