@stapel/search-react 0.12.0 → 0.14.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 (43) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/MODULE.md +7 -3
  3. package/dist/default/LocationSummaryLine.d.ts +19 -0
  4. package/dist/default/LocationSummaryLine.d.ts.map +1 -1
  5. package/dist/default/LocationSummaryLine.js +111 -11
  6. package/dist/default/LocationSummaryLine.js.map +1 -1
  7. package/dist/default/SearchResultCard.d.ts.map +1 -1
  8. package/dist/default/SearchResultCard.js +83 -37
  9. package/dist/default/SearchResultCard.js.map +1 -1
  10. package/dist/default/cardPhotos.d.ts +65 -0
  11. package/dist/default/cardPhotos.d.ts.map +1 -0
  12. package/dist/default/cardPhotos.js +127 -0
  13. package/dist/default/cardPhotos.js.map +1 -0
  14. package/dist/i18n/es.d.ts.map +1 -1
  15. package/dist/i18n/es.js +3 -0
  16. package/dist/i18n/es.js.map +1 -1
  17. package/dist/i18n/keys.d.ts +10 -0
  18. package/dist/i18n/keys.d.ts.map +1 -1
  19. package/dist/i18n/keys.js +13 -0
  20. package/dist/i18n/keys.js.map +1 -1
  21. package/dist/i18n/ru.d.ts.map +1 -1
  22. package/dist/i18n/ru.js +3 -0
  23. package/dist/i18n/ru.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/model/runtime.d.ts +40 -3
  28. package/dist/model/runtime.d.ts.map +1 -1
  29. package/dist/model/runtime.js +2 -1
  30. package/dist/model/runtime.js.map +1 -1
  31. package/llms.txt +3 -3
  32. package/manifest.json +14 -3
  33. package/nav-manifest.json +1 -1
  34. package/package.json +5 -5
  35. package/src/analytics/generated/events.json +1 -1
  36. package/src/default/LocationSummaryLine.tsx +150 -22
  37. package/src/default/SearchResultCard.tsx +157 -71
  38. package/src/default/cardPhotos.ts +195 -0
  39. package/src/i18n/es.ts +3 -0
  40. package/src/i18n/keys.ts +13 -0
  41. package/src/i18n/ru.ts +3 -0
  42. package/src/index.ts +1 -0
  43. package/src/model/runtime.ts +43 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # @stapel/search-react
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 396e32e: The phone SERP's location row is one compact line again, and stops overlapping itself.
8
+
9
+ Measured on a live 390×844 SERP, in both themes, on every result page — the
10
+ category listing, the text search and every filter slice:
11
+
12
+ ```
13
+ {"kind":"clipped-left","t":"A chosen place on the map","x":-4}
14
+ {"kind":"overlap","a":"· Within 25 km","b":"Filters","px":43}
15
+ ```
16
+
17
+ On screen that read as "…hosen place on the map · Within 25 kmlters", with the
18
+ red active-filter plaque floating in the top-right corner of the page attached
19
+ to nothing. At 1440 it was clean, which is why nothing before this caught it.
20
+
21
+ **Three causes, none of them the flex row itself.**
22
+
23
+ 1. An antd `<Button>` CENTRES its content. `minWidth: 0` let the left half
24
+ shrink and nothing clipped what was inside it, so the label overflowed its
25
+ box symmetrically — off the left edge of the screen at `x = -4` and 43px
26
+ across the word "Filters" at the other end. A shrunk box with no `overflow`
27
+ is not a truncation, it is an overlap.
28
+ 2. Nothing declared which end may shrink. Both were `1 1 auto`, so a long place
29
+ name took width from a word that must never lose any.
30
+ 3. The count was an antd `<Badge count>` — an absolutely positioned `sup` hung
31
+ off the top-right CORNER of what it wraps. At the trailing edge of a
32
+ full-width row that puts it outside the row entirely.
33
+
34
+ **Now:** `place · radius` left, `Filters` right, on one line. The left half is
35
+ the only one that grows or shrinks and it truncates with an ellipsis (`display:
36
+ block` on the label is load-bearing — `text-overflow` does nothing on a flex
37
+ box, which is how the first attempt cut the name mid-glyph); the right half is
38
+ `flex: 0 0 auto`; the count is a pill IN the flow beside the word it counts
39
+ for. The place and the radius now share ONE truncating label, so the radius can
40
+ no longer travel past the label's end on its own.
41
+
42
+ The rules that had to reach inside the button ship as a hoisted stylesheet
43
+ (`locationLineCss`, the pattern `<ListingCard>` and `<SkinCarousel>` use),
44
+ since a descendant rule is not expressible as an inline style.
45
+
46
+ `search-location-filters-badge` is now the trailing group rather than an antd
47
+ `<Badge>`; the count carries `data-testid="search-location-filters-count"`, and
48
+ the truncating label `data-testid="search-location-label"`.
49
+
50
+ A new skin demo variant, `long-place`, photographs the measured case: a place
51
+ name a geocoder really returns, at 390px, with a radius and a count on the same
52
+ line.
53
+
54
+ ## 0.13.0
55
+
56
+ ### Minor Changes
57
+
58
+ - e76ab28: The default SERP card draws the photos — the whole gallery, not one, and not none.
59
+
60
+ `<SearchResultCard>` read `card.image` as an object with a `url` key and fell
61
+ back to `card.image_url`. Neither is a shape this fleet emits, so **every
62
+ consumer that did not pass its own `renderCard` got a card with no photo at
63
+ all**:
64
+
65
+ - `image_url` is a convention nothing in the fleet writes. It was declared in
66
+ `GENERIC_CARD_FIELDS`, drawn in the demos from a data URI, and never once
67
+ served by a backend.
68
+ - `card.image` IS emitted — `stapel-classified`'s search projection stores it —
69
+ and it is a plain `<type>/<hash>` CDN reference **string**, not an object.
70
+ - the one rich shape that exists (chat's subject card, which serves the same
71
+ CDN render descriptor its attachments carry) has `ref` + `variants[]` and no
72
+ top-level `url`, so the `"url" in rich` guard rejected it too.
73
+
74
+ **What the card reads now** (`default/cardPhotos.ts`, unit-tested as data):
75
+ `images[]` first — the whole seller-ordered gallery `stapel-classified` 0.7.0
76
+ projects, capped by its `CARD_IMAGES_LIMIT` — with the singular `image` as the
77
+ fallback for a doc type that never grew a list. Never both: `image` IS
78
+ `images[0]`, so reading the singular after the list would draw the first photo
79
+ twice. Each entry may be a CDN reference, a URL the doc type stored itself (a
80
+ reference is `<type>/<hash>`: no scheme, no leading slash, so the two are told
81
+ apart by shape and never by a guess), or a render descriptor, whose whole
82
+ variant ladder survives so `<Image>` still has tiers to choose between.
83
+
84
+ **How a reference becomes a picture: a new `resolveImage` seam** on
85
+ `createSearchRuntime`, the same seam `@stapel/listings-react` states and the
86
+ same function a container passes to both. No contract in this fleet resolves a
87
+ stranger's reference — stapel-cdn's `file/exists/` is owner-scoped — so the
88
+ deployment hands its own knowledge in once rather than having a library invent
89
+ a URL convention nobody agreed to.
90
+
91
+ **A gallery is a strip.** Two or more photos render as a `<SkinCarousel>` with
92
+ the peek and the position indicator (one photo gets neither: a sliver of a next
93
+ slide is an affordance for something that is not there). The strip is a
94
+ **sibling** of the card's anchor, never a child — a horizontal swipe that ends
95
+ inside an `<a>` can be delivered as a click, which is the defect that makes
96
+ phone galleries unusable.
97
+
98
+ **Three honest answers about a photo, drawn as three different things.** A card
99
+ with no photo field reserves nothing (a text corpus is not a gallery with holes
100
+ in it); a card whose references nothing resolved draws the well and _says_ the
101
+ photo is unavailable — that is what an unwired `resolveImage` looks like, and a
102
+ sentence gets it fixed where an empty grey box does not; anything else is the
103
+ strip.
104
+
105
+ BREAKING for a host that stored `card.image_url`: that field is no longer read
106
+ (nothing in the fleet wrote it). Store the URL in `image`/`images` instead — a
107
+ stored URL still needs no resolver.
108
+
109
+ New i18n keys in all three bundles: `search.results.photos`,
110
+ `search.results.photo_alt`, `search.results.photo_unavailable`.
111
+
3
112
  ## 0.12.0
4
113
 
5
114
  ### Minor Changes
package/MODULE.md CHANGED
@@ -112,9 +112,13 @@ are props, not dependencies (spec §6.2 items 1–2):
112
112
 
113
113
  - **`renderCard`** — `<ListingCard>` from `@stapel/listings-react/default`. The
114
114
  generic card here is a working default over a documented set of conventional
115
- `card` fields (`title`, `price`, `currency`, `location`, `image_url`), because
116
- `SearchItem.card` is deliberately free-form: "stored row fields, so a result
117
- page costs one query".
115
+ `card` fields (`title`, `price`, `currency`, `location`, `images`, `image`,
116
+ `url`), because `SearchItem.card` is deliberately free-form: "stored row
117
+ fields, so a result page costs one query". `images` is the whole gallery in
118
+ seller order (stapel-classified 0.7.0) and `image` is `images[0]`; both hold
119
+ opaque `<type>/<hash>` CDN references, so the card draws them through the
120
+ runtime's `resolveImage` seam — the same seam `@stapel/listings-react`
121
+ states, and the same function a container passes to both.
118
122
  - **`categoryFeatures`** — the category's feature schema, from
119
123
  `categories-react`. The server sends `{value: count}` and no labels; the
120
124
  captions are translation keys in that schema.
@@ -1,5 +1,24 @@
1
1
  import type { ReactElement, ReactNode } from "react";
2
2
  import type { GeoFilterSlotProps } from "./FacetPanelPane.js";
3
+ /** The class the row carries, for {@link locationLineCss}. */
4
+ export declare const LOCATION_LINE_CLASS = "stapel-search-location-line";
5
+ /** The class the shrinking half carries. */
6
+ export declare const LOCATION_LINE_WHERE_CLASS = "stapel-search-location-line-where";
7
+ /** The class the truncating label carries. */
8
+ export declare const LOCATION_LINE_LABEL_CLASS = "stapel-search-location-line-label";
9
+ /** The class the fixed half carries. */
10
+ export declare const LOCATION_LINE_END_CLASS = "stapel-search-location-line-end";
11
+ /** The `href` the hoisted stylesheet is deduplicated by. */
12
+ export declare const LOCATION_LINE_STYLE_HREF = "stapel-search-location-line";
13
+ /**
14
+ * The rules that keep the line ONE line — and that an inline style cannot
15
+ * reach, because they belong on the `<span>` antd wraps a button's children
16
+ * in. See the file header (defect C12) for what each of them stops.
17
+ *
18
+ * Static: nothing here varies per instance, so one hoisted copy serves a page
19
+ * in either theme.
20
+ */
21
+ export declare function locationLineCss(): string;
3
22
  export interface LocationSummaryLineProps {
4
23
  /**
5
24
  * The location control the sheet opens onto — the same slot the panel and
@@ -1 +1 @@
1
- {"version":3,"file":"LocationSummaryLine.d.ts","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAe9D,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACnE;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAyBD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,YAAY,CAwFd"}
1
+ {"version":3,"file":"LocationSummaryLine.d.ts","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"AAkEA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AACjE,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAC7E,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAC7E,wCAAwC;AACxC,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE,4DAA4D;AAC5D,eAAO,MAAM,wBAAwB,gCAAgC,CAAC;AAEtE;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CA4BxC;AAsCD,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACnE;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAyBD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,YAAY,CA+Gd"}
@@ -28,29 +28,129 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
28
28
  *
29
29
  * `<FilterChips>`'s leading chip is a 32px circle: a number inside it is a
30
30
  * number nobody reads, so it shows a dot. This is a full-width row with a word
31
- * on it, so the badge has room to say HOW MANY constraints are applied — which
32
- * is the difference between "something is filtered" and "four things are, and
33
- * that is why there are three results". Both read the same
34
- * `activeFilters` off the URL state; neither invents a second counter.
31
+ * on it, so it has room to say HOW MANY constraints are applied — which is the
32
+ * difference between "something is filtered" and "four things are, and that is
33
+ * why there are three results". Both read the same `activeFilters` off the URL
34
+ * state; neither invents a second counter.
35
+ *
36
+ * ## The row overlapped itself on a phone, and why (defect C12)
37
+ *
38
+ * Measured on a live 390px SERP, in both themes, on every result page:
39
+ *
40
+ * ```
41
+ * {"kind":"clipped-left","t":"A chosen place on the map","x":-4}
42
+ * {"kind":"overlap","a":"· Within 25 km","b":"Filters","px":43}
43
+ * ```
44
+ *
45
+ * Three separate causes, and none of them was the flex row itself:
46
+ *
47
+ * 1. **An antd `<Button>` CENTRES its content.** `minWidth: 0` let the left
48
+ * item shrink, and nothing clipped what was inside it, so the label
49
+ * overflowed its box symmetrically — off the left edge of the screen at
50
+ * `x = -4` and 43px across the word "Filters" at the other end. A shrunk
51
+ * box with no `overflow` is not a truncation, it is an overlap.
52
+ * 2. **Nothing declared which end may shrink.** Both ends were `1 1 auto`, so
53
+ * a long place name took width from a word that must never lose any.
54
+ * 3. **The count was an antd `<Badge count>`,** which is an absolutely
55
+ * positioned `sup` hung off the top-right CORNER of what it wraps. At the
56
+ * trailing edge of a full-width row that lands it outside the row entirely
57
+ * — a red plaque floating in the corner of the page, attached to nothing
58
+ * (measured at `y = 72` for a row whose own line is at `y = 93`).
59
+ *
60
+ * So: one compact line, `place · radius` on the left and `Filters` right; the
61
+ * left is the only half that shrinks and it truncates with an ellipsis; the
62
+ * count rides IN the flow beside the word it counts for. The first two rules
63
+ * are what an inline style cannot reach (they belong on the wrapper antd puts
64
+ * around a button's children), which is why this file hoists a stylesheet the
65
+ * way `<ListingCard>` and `<SkinCarousel>` do.
35
66
  */
36
67
  import { useState } from "react";
37
- import { Badge, Button, Flex } from "antd";
68
+ import { Button, Flex } from "antd";
38
69
  import { useT } from "@stapel/core";
39
- import { spacing } from "@stapel/tokens";
70
+ import { cssVar, fontSize, radii, spacing } from "@stapel/tokens";
40
71
  import { useSearchState } from "../headless/SearchStateProvider.js";
41
72
  import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
42
73
  import { geoSummaryFallback } from "./FacetPanelPane.js";
43
74
  import { GeoSheet, SUMMARY_GEO_TEST_IDS } from "./geoSheet.js";
75
+ /** The class the row carries, for {@link locationLineCss}. */
76
+ export const LOCATION_LINE_CLASS = "stapel-search-location-line";
77
+ /** The class the shrinking half carries. */
78
+ export const LOCATION_LINE_WHERE_CLASS = "stapel-search-location-line-where";
79
+ /** The class the truncating label carries. */
80
+ export const LOCATION_LINE_LABEL_CLASS = "stapel-search-location-line-label";
81
+ /** The class the fixed half carries. */
82
+ export const LOCATION_LINE_END_CLASS = "stapel-search-location-line-end";
83
+ /** The `href` the hoisted stylesheet is deduplicated by. */
84
+ export const LOCATION_LINE_STYLE_HREF = "stapel-search-location-line";
85
+ /**
86
+ * The rules that keep the line ONE line — and that an inline style cannot
87
+ * reach, because they belong on the `<span>` antd wraps a button's children
88
+ * in. See the file header (defect C12) for what each of them stops.
89
+ *
90
+ * Static: nothing here varies per instance, so one hoisted copy serves a page
91
+ * in either theme.
92
+ */
93
+ export function locationLineCss() {
94
+ const row = `.${LOCATION_LINE_CLASS}`;
95
+ const where = `.${LOCATION_LINE_WHERE_CLASS}`;
96
+ const label = `.${LOCATION_LINE_LABEL_CLASS}`;
97
+ const end = `.${LOCATION_LINE_END_CLASS}`;
98
+ return [
99
+ // `min-width:0` on the row too: a flex item's default `min-width:auto`
100
+ // is what makes a nested flex row refuse to shrink at all.
101
+ `${row}{min-inline-size:0}`,
102
+ // The ONLY half that shrinks, and it CLIPS what it cannot show. An antd
103
+ // Button is already a flex row and it CENTRES its children, so without
104
+ // these two the box shrank and its content overflowed both edges at once
105
+ // — the whole defect: `x:-4` on the left, 43px over "Filters" on the
106
+ // right.
107
+ `${where}{flex:1 1 auto;min-inline-size:0;overflow:hidden;` +
108
+ `justify-content:flex-start}`,
109
+ // Whatever the button puts its children in has to be allowed to shrink;
110
+ // a flex item's `min-width:auto` refuses to go below its content.
111
+ `${where}>span{min-inline-size:0}`,
112
+ // `display:block` is load-bearing: `text-overflow` applies to a BLOCK
113
+ // container and does nothing on a flex one, so a label that had been
114
+ // turned into a flex box truncated with a hard cut mid-glyph instead of
115
+ // an ellipsis.
116
+ `${label}{display:block;min-inline-size:0;overflow:hidden;` +
117
+ `text-overflow:ellipsis;white-space:nowrap}`,
118
+ // The word a person is looking for never loses a pixel to a place name.
119
+ `${end}{flex:0 0 auto}`,
120
+ ].join("");
121
+ }
44
122
  /** The row. Both ends are text buttons, so the row reads as a line of type
45
123
  * rather than as two controls bolted onto a results page. */
46
124
  const ROW = { width: "100%" };
47
- /** Each end shrinks before it wraps; the location is the half that may
48
- * ellipsis, because a long place name must not push "Filters" off screen. */
125
+ /** The shrinking half. The flex rules it needs live in the hoisted sheet
126
+ * (they have to reach antd's own wrapper span); this is the chrome. */
49
127
  const LOCATION = {
50
- minWidth: 0,
51
128
  paddingInline: 0,
52
129
  textAlign: "start",
53
130
  };
131
+ /** The pin, which is not allowed to shrink either — a squashed glyph is
132
+ * noise, and it costs 16px. */
133
+ const PIN = { flex: "0 0 auto", display: "inline-flex" };
134
+ /**
135
+ * The count, IN the flow.
136
+ *
137
+ * An antd `<Badge count>` is an absolutely positioned `sup` on the corner of
138
+ * whatever it wraps, and at the trailing edge of a full-width row that puts it
139
+ * outside the row: a red plaque in the corner of the page attached to nothing.
140
+ * A pill beside the word is the same fact, in the line it belongs to, and it
141
+ * takes part in the layout instead of floating over it.
142
+ */
143
+ const COUNT = {
144
+ display: "inline-block",
145
+ minInlineSize: `${String(spacing[4])}px`,
146
+ paddingInline: spacing[1],
147
+ borderRadius: radii.full,
148
+ background: cssVar("brand"),
149
+ color: cssVar("text-on-accent"),
150
+ fontSize: fontSize.xs.fontSize,
151
+ lineHeight: `${String(spacing[4])}px`,
152
+ textAlign: "center",
153
+ };
54
154
  /** A map pin in `currentColor` — the house convention: an inline monochrome
55
155
  * SVG, no icon-font dependency, and it inherits the theme rather than carrying
56
156
  * a colour. `aria-hidden` because the text beside it is the name. */
@@ -72,9 +172,9 @@ export function LocationSummaryLine(props) {
72
172
  const where = geo === undefined
73
173
  ? t(SEARCH_I18N_KEYS.geoEverywhere)
74
174
  : (props.geoLabel ?? geoSummaryFallback(geo, t));
75
- return (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", justify: "space-between", gap: spacing[2], style: ROW, "data-testid": "search-location-summary", "data-geo": geo === undefined ? "off" : "on", children: [_jsxs(Button, { type: "link", style: LOCATION, icon: _jsx(PinGlyph, {}), "data-testid": "search-location-open", "data-analytics": "none", "data-analytics-reason": "opening the location sheet is a read, not a flow step", onClick: () => {
175
+ return (_jsxs(_Fragment, { children: [_jsx("style", { href: LOCATION_LINE_STYLE_HREF, precedence: "default", children: locationLineCss() }), _jsxs(Flex, { align: "center", justify: "space-between", gap: spacing[2], style: ROW, className: LOCATION_LINE_CLASS, "data-testid": "search-location-summary", "data-geo": geo === undefined ? "off" : "on", children: [_jsxs(Button, { type: "link", style: LOCATION, className: LOCATION_LINE_WHERE_CLASS, "data-testid": "search-location-open", "data-analytics": "none", "data-analytics-reason": "opening the location sheet is a read, not a flow step", onClick: () => {
76
176
  setOpen(true);
77
- }, children: [where, radius !== undefined && (_jsxs("span", { "data-testid": "search-location-radius", children: [" · ", radius] }))] }), _jsx(Badge, { count: activeFilters, size: "small", "data-testid": "search-location-filters-badge", children: _jsx(Button, { type: "link", style: { paddingInline: 0 }, "data-testid": "search-location-filters", "data-active": activeFilters > 0 ? "true" : "false", "data-analytics": "none", "data-analytics-reason": "opening the filter sheet is a read, not a flow step", onClick: props.onOpenAll, children: t(SEARCH_I18N_KEYS.filtersShort) }) })] }), _jsx(GeoSheet, { open: open, onClose: () => {
177
+ }, children: [_jsx("span", { style: PIN, children: _jsx(PinGlyph, {}) }), _jsxs("span", { className: LOCATION_LINE_LABEL_CLASS, "data-testid": "search-location-label", children: [where, radius !== undefined && (_jsxs("span", { "data-testid": "search-location-radius", children: [" · ", radius] }))] })] }), _jsxs(Flex, { align: "center", gap: spacing[1], className: LOCATION_LINE_END_CLASS, "data-testid": "search-location-filters-badge", children: [_jsx(Button, { type: "link", style: { paddingInline: 0 }, "data-testid": "search-location-filters", "data-active": activeFilters > 0 ? "true" : "false", "data-analytics": "none", "data-analytics-reason": "opening the filter sheet is a read, not a flow step", onClick: props.onOpenAll, children: t(SEARCH_I18N_KEYS.filtersShort) }), activeFilters > 0 && (_jsx("span", { style: COUNT, "data-testid": "search-location-filters-count", children: activeFilters }))] })] }), _jsx(GeoSheet, { open: open, onClose: () => {
78
178
  setOpen(false);
79
179
  }, testIds: SUMMARY_GEO_TEST_IDS, ...(props.renderGeoFilter !== undefined
80
180
  ? { renderGeoFilter: props.renderGeoFilter }
@@ -1 +1 @@
1
- {"version":3,"file":"LocationSummaryLine.js","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocationSummaryLine.js","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SearchResultCard.d.ts","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,wDAAwD;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,CAAC;AAQjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAOhD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAoBpB;AAiFD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAgGrE"}
1
+ {"version":3,"file":"SearchResultCard.d.ts","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,wDAAwD;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,CAAC;AAQjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAWhD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAoBpB;AA0KD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAmFrE"}
@@ -19,11 +19,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  *
20
20
  * ── Two things this card used to get wrong ────────────────────────────────
21
21
  *
22
- * 1. **It declared `image_url` and drew nothing.** A text-only card in a
23
- * classifieds search is not a card; the field was in `GENERIC_CARD_FIELDS`
24
- * and in no render path. It is drawn now, through `@stapel/image`, so the
25
- * aspect box lands before the network does and a dead URL renders a named
26
- * placeholder instead of the browser's torn-page icon.
22
+ * 1. **It read a photo shape nothing in this fleet emits.** `card.image` was
23
+ * read as an object with a `url` key and `card.image_url` as the fallback;
24
+ * what the fleet actually stores is a `<type>/<hash>` CDN reference, and
25
+ * where it does store an object that object carries `ref` + `variants[]`
26
+ * and no top-level `url`. So every consumer that did not pass its own
27
+ * `renderCard` got a card with no photo at all. `cardPhotos.ts` reads the
28
+ * real shapes, through the runtime's `resolveImage` seam, and the whole
29
+ * `images[]` gallery rather than one photo.
27
30
  * 2. **The marking's EXPLANATION was in a `Tooltip`.** Touch has no hover, so
28
31
  * on the device most of this traffic arrives from, the sentence the law is
29
32
  * actually about was unreachable — the tag said "Promoted" and nothing on
@@ -47,8 +50,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
47
50
  import { useMemo } from "react";
48
51
  import { Card, Flex, Typography } from "antd";
49
52
  import { Image } from "@stapel/image";
53
+ import { SkinCarousel } from "@stapel/tokens-antd/skin";
50
54
  import { useFormat, useT } from "@stapel/core";
51
55
  import { cssVar, fontSize, fontWeight, radii, spacing } from "@stapel/tokens";
56
+ import { useSearchRuntime } from "../model/context.js";
57
+ import { readCardPhotos } from "./cardPhotos.js";
52
58
  import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
53
59
  function text(value) {
54
60
  if (typeof value === "string" && value.length > 0)
@@ -67,7 +73,11 @@ export const GENERIC_CARD_FIELDS = [
67
73
  "price",
68
74
  "currency",
69
75
  "location",
70
- "image_url",
76
+ // The gallery, and the singular first photo the projection keeps beside it
77
+ // (`images[0]`). Both hold CDN references; see `cardPhotos.ts`. The old
78
+ // `image_url` is gone — no backend in this fleet ever wrote it.
79
+ "images",
80
+ "image",
71
81
  "url",
72
82
  ];
73
83
  /**
@@ -137,41 +147,74 @@ const PROMOTED_TAG = {
137
147
  lineHeight: `${String(fontSize.md.lineHeight)}px`,
138
148
  whiteSpace: "nowrap",
139
149
  };
140
- /** The photo's box: a 4:3 well the image fills, drawn before it loads. */
141
- const PHOTO = {
150
+ /**
151
+ * The shape of one photo well, everywhere on this card. Declared once so the
152
+ * placeholder and the loaded photo cannot drift into two different crops, and
153
+ * so the well is on screen before the network answers.
154
+ */
155
+ const CARD_PHOTO_ASPECT = "4 / 3";
156
+ /** The placeholder well: the box the photo would have occupied. */
157
+ const PHOTO_ABSENT = {
158
+ display: "flex",
159
+ flexDirection: "column",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ gap: spacing[1],
142
163
  width: "100%",
143
- aspectRatio: "4 / 3",
164
+ aspectRatio: CARD_PHOTO_ASPECT,
144
165
  overflow: "hidden",
145
166
  borderRadius: radii.md,
146
167
  background: cssVar("surface-sunken"),
168
+ color: cssVar("text-muted"),
147
169
  };
170
+ /** The slide's own fill — the strip's well owns the shape. */
171
+ const PHOTO_FILL = { width: "100%", height: "100%" };
172
+ /** A camera outline in `currentColor` — no icon dependency, and it inherits
173
+ * the theme rather than carrying a colour. `aria-hidden` because the sentence
174
+ * beside it is the label. */
175
+ function CameraGlyph() {
176
+ return (_jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M3 8.5A2.5 2.5 0 0 1 5.5 6h1.7l1.2-2h6.2l1.2 2h1.7A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5z" }), _jsx("circle", { cx: "12", cy: "12.5", r: "3.2" })] }));
177
+ }
148
178
  /**
149
- * A plain `image_url` as the descriptor `<Image>` consumes.
179
+ * The card's photos the whole gallery, as a swipeable strip.
180
+ *
181
+ * ── Why the strip is OUTSIDE the card's anchor ────────────────────────────
150
182
  *
151
- * A doc type that stores a bare URL has no variant ladder and no dimensions —
152
- * `source: "link"` is exactly that case, and `<Image>` degrades to the single
153
- * URL rather than pretending to shop a tier. A doc type that stores the whole
154
- * `StapelImage` snapshot (`card.image`) gets the ladder, blur-up and all.
183
+ * `<SkinCarousel>` is a scroll container with its own tab stop, and a
184
+ * horizontal swipe that ends inside an `<a>` is a swipe the browser may
185
+ * deliver as a click. Inside the anchor, every attempt to look at photo two
186
+ * would navigate to the result — the defect that makes phone galleries
187
+ * unusable. So it sits above the anchor as a sibling, exactly as
188
+ * `<ListingSerpCard>` does it, and the anchor still covers everything a
189
+ * person reads.
190
+ *
191
+ * Three states, and they are three different facts. No photo FIELD at all —
192
+ * a doc type that indexes text — draws nothing, because reserving a 4:3 well
193
+ * for a corpus that has no photos is a hole in every row. A field with
194
+ * nothing behind it draws the well and says the photo is unavailable: that is
195
+ * usually an unwired `resolveImage`, and a sentence gets it fixed where an
196
+ * empty grey box does not. Anything else is the strip.
155
197
  */
156
- function cardImage(card) {
157
- const rich = card["image"];
158
- if (rich !== null && typeof rich === "object" && "url" in rich) {
159
- return rich;
198
+ function CardPhotoStrip(props) {
199
+ const t = useT();
200
+ const { photos, title } = props;
201
+ if (photos.stored === 0)
202
+ return null;
203
+ if (photos.images.length === 0) {
204
+ const caption = t(SEARCH_I18N_KEYS.resultsPhotoUnavailable);
205
+ return (_jsxs("div", { role: "img", "aria-label": caption, "data-testid": "search-result-photo-absent", style: PHOTO_ABSENT, children: [_jsx(CameraGlyph, {}), _jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, children: caption })] }));
160
206
  }
161
- const url = text(card["image_url"]);
162
- if (url === undefined)
163
- return undefined;
164
- return {
165
- source: "link",
166
- url,
167
- mime: null,
168
- width: null,
169
- height: null,
170
- aspect: null,
171
- square: false,
172
- preview_b64: null,
173
- variants: [],
174
- };
207
+ const total = photos.images.length;
208
+ // A one-photo strip gets neither a peek nor dots: the sliver of a next
209
+ // slide is an affordance for something that is there.
210
+ const many = total > 1;
211
+ return (_jsx(SkinCarousel, { label: t(SEARCH_I18N_KEYS.resultsPhotos), aspectRatio: CARD_PHOTO_ASPECT, peek: many, dots: many, "data-testid": "search-result-photos", children: photos.images.map((image, index) => (_jsx(Image, { meta: image, alt: many
212
+ ? t(SEARCH_I18N_KEYS.resultsPhotoAlt, {
213
+ index: index + 1,
214
+ total,
215
+ title,
216
+ })
217
+ : t(SEARCH_I18N_KEYS.resultsImageAlt, { title }), fit: "cover", "data-testid": "search-result-photo", style: PHOTO_FILL }, `${String(index)}:${image.url}`))) }));
175
218
  }
176
219
  /**
177
220
  * The whole card as ONE tap target.
@@ -193,6 +236,7 @@ export function SearchResultCard(props) {
193
236
  const t = useT();
194
237
  const format = useFormat();
195
238
  const card = props.item.card;
239
+ const resolve = useSearchRuntime().resolveImage;
196
240
  const title = text(card["title"]) ?? t(SEARCH_I18N_KEYS.resultsUntitled);
197
241
  const price = formatCardPrice(format, text(card["price"]), text(card["currency"]));
198
242
  const href = text(card["url"]);
@@ -202,10 +246,12 @@ export function SearchResultCard(props) {
202
246
  km: props.item.distance_km.toFixed(1),
203
247
  })
204
248
  : undefined;
205
- // Memoised on the two fields it reads: a fresh `meta` identity every render
206
- // is a load `<Image>` has to decide is or is not the same one.
207
- const image = useMemo(() => cardImage(card), [card]);
208
- const body = (_jsxs(Flex, { vertical: true, gap: spacing[2], children: [image !== undefined && (_jsx("div", { style: PHOTO, "data-testid": "search-result-photo", children: _jsx(Image, { meta: image, alt: t(SEARCH_I18N_KEYS.resultsImageAlt, { title }), fit: "cover", style: { width: "100%", height: "100%" } }) })), _jsxs(Flex, { vertical: true, gap: spacing[1], children: [_jsx(Typography.Text, { strong: true, children: title }), price !== undefined && (_jsx(Typography.Text, { strong: true, style: { fontSize: fontSize.lg.fontSize }, "data-testid": "search-result-price", children: price })), (location !== undefined || distance !== undefined) && (_jsx(Typography.Text, { type: "secondary", children: [location, distance].filter((v) => v !== undefined).join(" · ") }))] })] }));
209
- return (_jsx(Card, { size: "small", "data-testid": "search-result-card", "data-promoted": props.item.promoted ? "true" : "false", ...(href !== undefined ? { hoverable: true } : {}), styles: { body: { padding: spacing[3] } }, children: _jsxs(Flex, { vertical: true, gap: spacing[2], children: [href === undefined ? (body) : (_jsx("a", { href: href, style: CARD_LINK, "data-testid": "search-result-link", children: body })), props.item.promoted && (_jsxs(Flex, { vertical: true, gap: spacing[1], "data-testid": "search-result-promotion", children: [_jsx("span", { style: PROMOTED_TAG, "data-testid": "search-result-promoted", children: t(SEARCH_I18N_KEYS.resultsPromoted) }), _jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, "data-testid": "search-result-promoted-hint", children: t(SEARCH_I18N_KEYS.resultsPromotedHint) })] }))] }) }));
249
+ // Memoised on the card and the resolver: a host resolver is a plain
250
+ // function returning a fresh object, so resolving inline hands `<Image>` a
251
+ // new `meta` identity on every render — a load it then has to decide is or
252
+ // is not the same one.
253
+ const photos = useMemo(() => readCardPhotos(card, resolve), [card, resolve]);
254
+ const body = (_jsxs(Flex, { vertical: true, gap: spacing[1], children: [_jsx(Typography.Text, { strong: true, children: title }), price !== undefined && (_jsx(Typography.Text, { strong: true, style: { fontSize: fontSize.lg.fontSize }, "data-testid": "search-result-price", children: price })), (location !== undefined || distance !== undefined) && (_jsx(Typography.Text, { type: "secondary", children: [location, distance].filter((v) => v !== undefined).join(" · ") }))] }));
255
+ return (_jsx(Card, { size: "small", "data-testid": "search-result-card", "data-promoted": props.item.promoted ? "true" : "false", ...(href !== undefined ? { hoverable: true } : {}), styles: { body: { padding: spacing[3] } }, children: _jsxs(Flex, { vertical: true, gap: spacing[2], children: [_jsx(CardPhotoStrip, { photos: photos, title: title }), href === undefined ? (body) : (_jsx("a", { href: href, style: CARD_LINK, "data-testid": "search-result-link", children: body })), props.item.promoted && (_jsxs(Flex, { vertical: true, gap: spacing[1], "data-testid": "search-result-promotion", children: [_jsx("span", { style: PROMOTED_TAG, "data-testid": "search-result-promoted", children: t(SEARCH_I18N_KEYS.resultsPromoted) }), _jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, "data-testid": "search-result-promoted-hint", children: t(SEARCH_I18N_KEYS.resultsPromotedHint) })] }))] }) }));
210
256
  }
211
257
  //# sourceMappingURL=SearchResultCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchResultCard.js","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchResultCard.js","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"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"}