@rudra-js/react 0.8.2 → 0.9.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.
package/README.md CHANGED
@@ -6,18 +6,21 @@ rudra-js is an open-source TypeScript library (MIT) you install from npm. A lang
6
6
 
7
7
  It is not a hosted platform, a visual or low-code app builder, or a workspace you sign up for.
8
8
 
9
- Renders a component specification from
10
- [`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core) with React
11
- 18 or 19.
9
+ This package renders a component spec with React 18 or 19. The spec is the description of the block
10
+ that [`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core)
11
+ generates.
12
12
 
13
- The renderer and its registry are documented at [rudrajs.com](https://rudrajs.com/docs#RudraComponent).
13
+ The reference for [`RudraComponent`](https://rudrajs.com/reference/react/RudraComponent) and [`extendRegistry`](https://rudrajs.com/reference/react/extendRegistry) is on rudrajs.com.
14
14
 
15
- The components are plain functions with no hooks, state or effects. They work with
16
- `renderToString`, in a client-rendered app, and as Server Components in a setup that supports them,
17
- such as the Next.js App Router. Rendered on the server, the
18
- recommendation area arrives in the initial HTML response and needs no client JavaScript, so it never
19
- pops in the way a client-fetched recommendation rail does, and a crawler that doesn't run JavaScript
20
- still reads it.
15
+ The components are plain functions with no hooks, state or effects. You can render them:
16
+
17
+ - with `renderToString`
18
+ - in a client-rendered app
19
+ - as Server Components, in a setup that supports them, such as the Next.js App Router
20
+
21
+ Rendered on the server, the recommendations arrive in the initial HTML response and need no client
22
+ JavaScript. They never pop in late the way a client-fetched recommendation rail does. A crawler that
23
+ doesn't run JavaScript still reads them.
21
24
 
22
25
  ## Install
23
26
 
@@ -25,21 +28,28 @@ still reads it.
25
28
  npm install @rudra-js/react@latest @rudra-js/core@latest @rudra-js/attested@latest react react-dom
26
29
  ```
27
30
 
28
- Run the same command in a new app, in one that already uses rudra-js, and to upgrade. If your app
29
- has `@rudra-js/anthropic`, add it to the command with `@latest` too. If it has `@rudra-js/a2ui`, use
30
- the install line in that package's README instead, since it also names the two A2UI packages. The
31
- `@rudra-js` packages are published together, and each one's peer ranges on the others start at its
32
- own version. For a bare name npm stays inside the range your `package.json` already has, so without
33
- `@latest` an app on an older core can get an `ERESOLVE`. `react` and `react-dom` carry no version,
34
- so npm and pnpm keep a React 18 app on 18. Name both: npm moves each to the newest release in its
35
- range, and React 19 won't load `react-dom` at a different version from `react`.
36
-
37
- Both `@rudra-js/core` and `react` are peer dependencies. The specification you pass in comes from
38
- your copy of core, and the elements this renders have to come from the same React your app renders.
39
- With two copies of either, you'd get a spec that fails its own type check, or a component tree React
40
- refuses to render. `@rudra-js/attested` is core's peer: npm and pnpm add it for you, Yarn doesn't.
41
- Yarn does move a bare name to its latest version, so leave out `react` and `react-dom` if your app
42
- already has React.
31
+ Use this same command to install and to upgrade, whether or not your app already uses rudra-js.
32
+
33
+ - If your app has `@rudra-js/anthropic`, add it to the command with `@latest` too.
34
+ - If your app has `@rudra-js/a2ui`, use the install line in that package's README instead. It also
35
+ names the two A2UI packages.
36
+
37
+ Keep `@latest` on every `@rudra-js` package. The packages are published together. Each one's peer
38
+ ranges on the others start at its own version. With a bare name, npm stays inside the range your
39
+ `package.json` already has. So without `@latest`, an app on an older core can get an `ERESOLVE`.
40
+
41
+ Leave `react` and `react-dom` without a version. Then npm and pnpm keep a React 18 app on 18. Do name
42
+ both of them, so npm moves each one to the newest release in its range. React 19 won't load a
43
+ `react-dom` at a different version from `react`.
44
+
45
+ `@rudra-js/core` and `react` are peer dependencies. The spec you pass in comes from your copy of core.
46
+ The elements this package renders have to come from the same React your app renders. With two copies
47
+ of core, the spec fails its own type check. With two copies of React, React refuses to render the
48
+ component tree.
49
+
50
+ `@rudra-js/attested` is core's peer dependency. npm and pnpm add it for you. Yarn doesn't. Yarn does
51
+ move a bare name to its latest version, so with Yarn, leave `react` and `react-dom` out of the command
52
+ if your app already has React.
43
53
 
44
54
  ```tsx
45
55
  import { RudraComponent } from '@rudra-js/react';
@@ -51,9 +61,8 @@ import { RudraComponent } from '@rudra-js/react';
51
61
 
52
62
  ## What comes from where
53
63
 
54
- A rendered component rests on this split. The model decides how things are arranged and what the
55
- words are. Every fact about a product is read from your catalog as the page is served, and whatever
56
- the model wrote is rendered as escaped text.
64
+ The model decides how the block is laid out and what the words say. Every fact about a product is
65
+ read from your catalog when the page is served. Whatever the model wrote is rendered as escaped text.
57
66
 
58
67
  | Decided by the model | Decided by your catalog |
59
68
  | ------------------------------------------------------------------- | ----------------------- |
@@ -61,40 +70,41 @@ the model wrote is rendered as escaped text.
61
70
  | Tone, headline, the words in each block | Every price |
62
71
  | Only in per-shopper mode: which products, and how each is described | Every image and link |
63
72
 
64
- In the default cohort mode core fills in the products, their order and the reason under each,
65
- per request, and a badge the model wrote gets dropped. The model picks those only under
66
- `generation: 'per-shopper'`. In either mode it still chooses the product a hero names. You'll find
67
- the full split in
73
+ The default is cohort mode, where shoppers who look alike share one spec. In cohort mode, core
74
+ fills in the products and their order for each request. It also writes the reason under each product.
75
+ It drops any badge the model wrote. Under `generation: 'per-shopper'`, the model makes those picks
76
+ itself. In both modes, the model chooses the product a hero names. The full split is in
68
77
  [What the model decides, by mode](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core#what-the-model-decides-by-mode).
69
78
 
70
- The specification has no field carrying a title, a price, an image or a URL. Product facts are
71
- resolved at render time from `products`, keyed by a SKU reconciliation has already checked.
79
+ The spec has no field for a title, a price, an image or a URL. The component looks up each product in
80
+ `products` by a SKU that core has already checked.
81
+
82
+ **Validate `products` with `productSchema` from `@rudra-js/core`**, the same schema the candidate
83
+ products you sent to core already passed. This prop is a second way for data to reach the page.
84
+
85
+ `imageUrl` ends up in an `<img src>`. `productSchema` rejects a protocol-relative URL such as
86
+ `//evil.example/pixel.png`, and it rejects a `data:` URL. It reads a path the way a browser does, so it
87
+ also rejects `/\evil.example/pixel.png` and the same trick written with a tab or a newline in it. A
88
+ browser resolves each of these to someone else's host. React blocks a `javascript:` URL on its own,
89
+ but none of these.
72
90
 
73
- **Validate `products` with `productSchema` from `@rudra-js/core`**, the same schema your candidates
74
- already passed. This prop is a second way for data to reach the page. `imageUrl` lands in an
75
- `<img src>`, and `productSchema` is what rejects a protocol-relative `//evil.example/pixel.png` or a `data:` URL.
76
- It reads a path the way a browser does, so `/\evil.example/pixel.png` and the same trick written
77
- with a tab or a newline in it are rejected too — each one resolves to someone else's host. React
78
- neutralises a `javascript:` URL by itself, but not any of those. And if a price isn't a finite
79
- number, it throws. A product that looks free is worse than a stack trace.
91
+ If a price isn't a finite number, rendering throws an error.
80
92
 
81
- A row flagged `isInStock: false` counts as a row that isn't there. Cards for it are dropped, a hero
82
- loses its link, and a bundle with one sold-out member disappears — the same as if you'd left the
83
- row out. Core only ever names a product that was in stock in the payload, but this catalog is read
84
- later and may be the fresher of the two.
93
+ A row with `isInStock: false` counts as a row that isn't there. Its cards are dropped. A hero that
94
+ names it loses its link. A bundle with it as a member disappears. Core only names products that were
95
+ in stock in the data you sent it. This catalog is read later, so it may be the fresher of the two.
85
96
 
86
97
  ## Styling
87
98
 
88
- We ship no CSS. A stylesheet of ours would only fight whatever your site already has. So out of the
89
- box the block renders as a run-on line — every card element is inline, so a card's parts sit on one
90
- line with spaces between them, and nothing separates one card from the next. That's the starting
91
- point you style from.
99
+ This package ships no CSS. Unstyled, the block renders as one run-on line. Every card element is
100
+ inline, so a card's parts sit on one line with spaces between them. Nothing separates one card from
101
+ the next. You style it from there.
92
102
 
93
- Check out `examples/shop/public/demo-styles.css`. It's a working stylesheet written against nothing
94
- but the table below, so copy it as a starting point rather than as a supported API. The example shop applies it by default, and `?styles=off`
95
- shows you the raw markup.
103
+ For a working stylesheet, see `examples/shop/public/demo-styles.css`. It uses nothing but the classes
104
+ in the table below. Copy it as a starting point. It isn't a supported API. The example shop applies
105
+ it by default, and `?styles=off` shows you the raw markup.
96
106
 
97
- Every element we emit carries a class. Here's all of them:
107
+ Every element the component renders carries a class. Here is the full list:
98
108
 
99
109
  | Where | Classes |
100
110
  | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -107,24 +117,29 @@ Every element we emit carries a class. Here's all of them:
107
117
  | A bundle | `.rudra-bundle`, `.rudra-bundle__label`, `.rudra-bundle__title`, `.rudra-bundle__body`, `.rudra-bundle__items`, `.rudra-bundle__item`, `.rudra-bundle__link`, `.rudra-bundle__price`, `.rudra-bundle__cta` |
108
118
  | A product card | `.rudra-card`, `.rudra-card--featured`, `.rudra-card__image`, `.rudra-card__body`, `.rudra-card__title`, `.rudra-card__price`, `.rudra-card__reason`, `.rudra-card__badge` |
109
119
 
110
- A few notes on those. `.rudra__rationale` only appears under `hasDiagnostics`.
111
- `.rudra-bundle__label` is your own name for the set, so it shows up only for a bundle you gave a
112
- `label`. And `className` is added alongside `rudra`, _never_ in place of it, so the child classes
113
- keep working.
120
+ A few notes on those:
114
121
 
115
- `.rudra-carousel__track` is meant to scroll horizontally, so give it `overflow-x: auto`. Nothing
116
- here uses JavaScript to scroll it for you. `.rudra-card--featured` is applied alongside
117
- `.rudra-card`, so write `.rudra-card--featured { ... }` after the base rule and let it layer on top.
122
+ - `.rudra__rationale` appears only under `hasDiagnostics`.
123
+ - `.rudra-bundle__label` holds your own name for the set. It shows up only for a bundle you gave a
124
+ `label`.
125
+ - Your `className` is added alongside `rudra`, never in place of it, so the child classes keep
126
+ working.
127
+ - `.rudra-carousel__track` is meant to scroll horizontally, so give it `overflow-x: auto`. No
128
+ JavaScript scrolls it for you.
129
+ - `.rudra-card--featured` is applied alongside `.rudra-card`. Write the `.rudra-card--featured { ... }`
130
+ rule after the base rule so it layers on top.
118
131
 
119
- `.rudra-banner__cta` and `.rudra-bundle__cta` are plain text, not links or buttons, because there is
120
- nothing for them to do. Don't style them as buttons unless a renderer of your own wraps them in one.
121
- The hero's call to action sits inside the product link, so that one is clickable. Every card in a
122
- carousel is a link, so tabbing through them scrolls the track, which keeps it usable by keyboard
123
- even with the scrollbar hidden.
132
+ `.rudra-banner__cta` and `.rudra-bundle__cta` are plain text, not links or buttons. Don't style
133
+ them as buttons unless a renderer of your own wraps them in one. The hero's call to action sits inside
134
+ the product link, so that one is clickable.
135
+
136
+ Every card in a carousel is a link. Tabbing through the cards scrolls the track, so the carousel
137
+ works by keyboard even with the scrollbar hidden.
124
138
 
125
139
  ### Attributes
126
140
 
127
- The same markup carries what the model decided, so you can hang styling or analytics off it.
141
+ The markup also carries what the model decided. You can hang styling or analytics off these
142
+ attributes.
128
143
 
129
144
  | Attribute | On | Value |
130
145
  | ------------------------ | ------------------------------ | ---------------------------------------------------------- |
@@ -136,8 +151,8 @@ The same markup carries what the model decided, so you can hang styling or analy
136
151
  | `data-rudra-sku` | a card, hero link, bundle item | The product, for click attribution |
137
152
  | `data-rudra-basis` | a card | Why the product was picked: `most_viewed`, `popular`, … |
138
153
 
139
- We leave `data-rudra-source` public so you can read hit rate and fallback share straight off a
140
- rendered page. Anything more specific than that appears only under `hasDiagnostics`, because it
154
+ `data-rudra-source` is always there, so you can read the cache hit rate and the fallback share
155
+ straight off a rendered page. Anything more specific appears only under `hasDiagnostics`, because it
141
156
  tells a visitor what you run and when it's failing:
142
157
 
143
158
  | Attribute | On | Value |
@@ -149,9 +164,8 @@ tells a visitor what you run and when it's failing:
149
164
 
150
165
  ## Replacing a renderer
151
166
 
152
- Swap any block for your own design-system component. The model isn't involved and the specification
153
- doesn't change, so it gains nothing here. It still picks from the same fixed vocabulary it always
154
- did.
167
+ Swap any block's renderer for a component from your own design system. Only the drawing changes. The
168
+ spec, and the block kinds the model picks from, stay the same.
155
169
 
156
170
  ```tsx
157
171
  import { RudraComponent, extendRegistry } from '@rudra-js/react';
@@ -164,63 +178,62 @@ const registry = extendRegistry({
164
178
  ```
165
179
 
166
180
  A grid, carousel or bundle block with nothing left in your catalog is dropped before any renderer
167
- runs — yours as well as ours. The component has to decide whether to draw the wrapper and its
168
- headline at all, and the only way to decide is to ask each block first. Hero, banner and copy always
169
- reach the renderer, since they don't depend on the catalog.
181
+ runs, yours or ours. The rest of the blocks still render. Hero, banner and copy blocks don't
182
+ depend on the catalog, so they always reach the renderer.
170
183
 
171
184
  ## Props
172
185
 
173
- | Prop | Notes |
174
- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
- | `spec` | Required. From `@rudra-js/core`. |
176
- | `products` | Required. A list of products, or anything keyed by SKU. See below, and the warning above. |
177
- | `bundles` | The sets your shop sells together. Only needed if a spec can carry a bundle block. |
178
- | `registry` | Replace some or all block renderers. |
179
- | `hrefForSku` | Defaults to `/product/{sku}`, URL-encoded. |
180
- | `formatPrice` | Defaults to `Intl.NumberFormat`, which knows how many decimal places each currency wants. It formats products, not bundles. |
181
- | `formatBundlePrice` | The same for a bundle's own price, in the currency the shop put on the set. The set's price and its currency come from the same object, so members priced in another currency change nothing. |
182
- | `locale` | Punctuates prices. Defaults to the **server's** locale, which is rarely the shopper's, so pass it if you serve more than one. |
183
- | `hasDiagnostics` | Adds the provider, the model name, the latency and the model's own reasoning to the markup. Off by default, since it tells a visitor which model you use and when it's failing. |
184
- | `className` | Added alongside `rudra`. |
186
+ | Prop | Notes |
187
+ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
188
+ | `spec` | Required. From `@rudra-js/core`. |
189
+ | `products` | Required. A list of products, or anything keyed by SKU. See below, and the `productSchema` warning above. |
190
+ | `bundles` | The sets your shop sells together. Only needed if a spec can carry a bundle block. |
191
+ | `registry` | Replaces some or all of the block renderers. |
192
+ | `hrefForSku` | Defaults to `/product/{sku}`, URL-encoded. |
193
+ | `formatPrice` | Formats product prices, not bundle prices. Defaults to `Intl.NumberFormat`, which knows how many decimal places each currency wants. |
194
+ | `formatBundlePrice` | Formats a bundle's own price, in the currency the shop put on the set. Defaults to `Intl.NumberFormat`, like `formatPrice`. The price and its currency come from the same bundle object, so members priced in another currency change nothing. |
195
+ | `locale` | Sets how prices are punctuated. Defaults to the **server's** locale, which is rarely the shopper's. Pass it if you serve more than one locale. |
196
+ | `hasDiagnostics` | Adds the provider, the model name, the latency and the model's own reasoning to the markup. Off by default. |
197
+ | `className` | Added alongside `rudra`. |
185
198
 
186
199
  ### What `products` may be
187
200
 
188
- A list of products, or anything keyed by SKU that answers `get(sku)` and `has(sku)`. A `Map` does
189
- it, and so does your own index. The renderers only ever call `get`, and `has` is how we tell a keyed
190
- catalog from a list, so if your catalog is too big to copy into a `Map` on every request, hand over
191
- a view of your own store instead. The prop is typed as a `ReadonlyMap`, so in TypeScript either
192
- implement `ReadonlyMap` or cast with `as unknown as ReadonlyMap<string, Product>`.
201
+ `products` is a list of products, or anything keyed by SKU that answers `get(sku)` and `has(sku)`. A
202
+ `Map` works, and so does your own index. The renderers only ever call `get`. `has` is how the
203
+ component tells a keyed catalog from a list. If your catalog is too big to copy into a `Map` on every
204
+ request, pass a view of your own store instead. The prop is typed as a `ReadonlyMap`, so in
205
+ TypeScript either implement `ReadonlyMap` or cast with `as unknown as ReadonlyMap<string, Product>`.
193
206
 
194
- We look for those two methods rather than for `instanceof Map`, which is per-realm. A `Map` arriving
195
- from a worker or a `node:vm` sandbox is a perfectly good catalog and fails `instanceof` anyway.
207
+ A `Map` from a worker or a `node:vm` sandbox works too, even though it fails `instanceof Map`.
196
208
 
197
- Anything that is neither a list nor keyed gets refused on the spot, with an error naming the prop.
198
- That's a `Set` of products, a plain object, or a `Map` that has been through JSON. Better a loud
199
- error than a quietly empty recommendation area.
209
+ Anything that is neither a list nor keyed is refused on the spot, with an error that names the prop.
210
+ That means a `Set` of products, a plain object, or a `Map` that has been through JSON.
200
211
 
201
212
  When there's nothing left to show, the component renders nothing at all. That covers a spec with no
202
- blocks, and one whose every product has left your catalog since it was generated. A single block
203
- goes the same way on its own: it drops out and the rest of the component carries on without it. An
204
- empty recommendation area, or a headline over an empty box, takes up space and tells the shopper the
205
- page is broken.
213
+ blocks, and a spec whose products have all left your catalog since it was generated.
206
214
 
207
215
  ### What `bundles` is
208
216
 
209
- The sets your shop sells together, the same way `products` is your catalog. The model only asks for
210
- a bundle block. It never invents one, and it _never_ sees a price.
217
+ `bundles` lists the sets your shop sells together, the same way `products` is your catalog. The
218
+ model only asks for a bundle block. It never invents a set, and it never sees a price.
211
219
 
212
- You offer the sets, and core picks which one fills each block. That happens inside
213
- `reconcileSpec` as the page is served, after the spec was generated rather than before, and it goes on what this
214
- shopper has in their basket, has looked at, or is browsing right now. The spec then carries the id
215
- it picked. This prop supplies the rest: that set's members, its price, the currency that price is
216
- in, and your name for it, so the component has something to draw.
220
+ You offer the sets, and core picks which one fills each bundle block. It does this in `reconcileSpec`
221
+ as the page is served, after the spec was generated. It goes by what this shopper has in their
222
+ basket, has looked at, or is browsing right now. The spec then carries the id of the set core picked.
223
+ This prop supplies the rest, so the component has something to draw: the set's members, its price,
224
+ the currency of that price, and your name for it.
217
225
 
218
226
  **Validate `bundles` with `bundleSchema` from `@rudra-js/core`, and pass the same list you sent to
219
- `parseTrackingInput`.** Core already checked that list — every member in stock, none of them
220
- disliked, none of them the product being looked at, no repeats, and the whole set inside the item
221
- budget — and then hands the renderer nothing but the id it chose. Pass a stale or different list
222
- here and you'll draw a set none of those checks ever saw, under an id that was proved against
223
- another one.
227
+ `parseTrackingInput`.** Core checked that list before it picked a set:
228
+
229
+ - every member is in stock
230
+ - no member is one the shopper disliked
231
+ - no member is the product the shopper is viewing
232
+ - no product shows up twice
233
+ - the whole set fits in what's left of `context.maxItems`, the most products the component may show
234
+
235
+ Core then hands the renderer only the id it chose. Pass a stale or different list here and you'll
236
+ draw a set none of those checks saw, under an id that was checked against another list.
224
237
 
225
238
  ## Licence
226
239
 
@@ -17,15 +17,15 @@ function formatMoney(price, currency, locale) {
17
17
  }
18
18
  export function defaultFormatPrice(product, locale) {
19
19
  if (!Number.isFinite(product.price)) {
20
- throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
21
- 'catalog objects must satisfy productSchema from @rudra-js/core');
20
+ throw new TypeError(`price for SKU ${product.sku} is ${String(product.price)}, not a finite number. ` +
21
+ 'Catalog objects must satisfy productSchema from @rudra-js/core');
22
22
  }
23
23
  return formatMoney(product.price, product.currency, locale);
24
24
  }
25
25
  export function defaultFormatBundlePrice(bundle, locale) {
26
26
  if (!Number.isFinite(bundle.price)) {
27
- throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number — ` +
28
- 'bundle objects must satisfy bundleSchema from @rudra-js/core');
27
+ throw new TypeError(`price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number. ` +
28
+ 'Bundle objects must satisfy bundleSchema from @rudra-js/core');
29
29
  }
30
30
  return formatMoney(bundle.price, bundle.currency, locale);
31
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,MAAe;IACnE,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B;YAChF,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,MAAe;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B;YAChF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,YAAY,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAsC,EACtC,GAAW;IAEX,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,OAAO,EAAE,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5D,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,QAAgB,EAAE,MAAe;IACnE,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC;IAChC,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,MAAe;IAClE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,iBAAiB,OAAO,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,yBAAyB;YAC/E,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,MAAe;IACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,SAAS,CACjB,oBAAoB,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB;YAC/E,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC5D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rudra-js/react",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "React renderer for rudra-js: renders an LLM-designed product-recommendation block from your own catalog, on the server or in the browser",
5
5
  "keywords": [
6
6
  "rudra-js",
@@ -54,14 +54,14 @@
54
54
  "access": "public"
55
55
  },
56
56
  "peerDependencies": {
57
- "@rudra-js/core": "^0.8.2",
57
+ "@rudra-js/core": "^0.9.0",
58
58
  "react": "^18.2.0 || ^19.0.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@rudra-js/core": "0.8.2",
61
+ "@rudra-js/core": "0.9.0",
62
62
  "@types/react": "^19.3.0",
63
63
  "@types/react-dom": "^19.3.0",
64
- "react": "^19.3.0",
65
- "react-dom": "^19.3.0"
64
+ "react": "^19.3.1",
65
+ "react-dom": "^19.3.1"
66
66
  }
67
67
  }
@@ -33,8 +33,8 @@ function formatMoney(price: number, currency: string, locale?: string): string {
33
33
  export function defaultFormatPrice(product: Product, locale?: string): string {
34
34
  if (!Number.isFinite(product.price)) {
35
35
  throw new TypeError(
36
- `price for SKU ${product.sku} is ${String(product.price)}, not a finite number — ` +
37
- 'catalog objects must satisfy productSchema from @rudra-js/core',
36
+ `price for SKU ${product.sku} is ${String(product.price)}, not a finite number. ` +
37
+ 'Catalog objects must satisfy productSchema from @rudra-js/core',
38
38
  );
39
39
  }
40
40
  return formatMoney(product.price, product.currency, locale);
@@ -43,8 +43,8 @@ export function defaultFormatPrice(product: Product, locale?: string): string {
43
43
  export function defaultFormatBundlePrice(bundle: Bundle, locale?: string): string {
44
44
  if (!Number.isFinite(bundle.price)) {
45
45
  throw new TypeError(
46
- `price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number — ` +
47
- 'bundle objects must satisfy bundleSchema from @rudra-js/core',
46
+ `price for bundle ${bundle.id} is ${String(bundle.price)}, not a finite number. ` +
47
+ 'Bundle objects must satisfy bundleSchema from @rudra-js/core',
48
48
  );
49
49
  }
50
50
  return formatMoney(bundle.price, bundle.currency, locale);