includio-cms 0.35.0 → 0.36.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/API.md CHANGED
@@ -1,8 +1,8 @@
1
- # includio-cms — Public API v0.35.0
1
+ # includio-cms — Public API v0.36.0
2
2
 
3
3
  > Auto-generated by `scripts/generate-api-md.ts`. Do not edit by hand.
4
4
 
5
- Entry points: **19** · Stable: **490** · Experimental: **4**
5
+ Entry points: **19** · Stable: **492** · Experimental: **4**
6
6
 
7
7
  Tags:
8
8
  - `@public` — frozen for v1.0; semver-protected.
@@ -450,6 +450,7 @@ Tags:
450
450
  - `interface FakturowniaAdapterOptions`
451
451
  - `filterUpcoming(variants: T[], config: VariantExpiryConfig | null, now?: Date): T[]` — Return the subset of `variants` that have not yet expired. Order is
452
452
  - `type GeowidgetConfigPreset = 'parcelcollect' | 'parcelsend' | 'parcelcollect247' | string`
453
+ - `type I18nTemplate = Record<string, string>`
453
454
  - `type I18nText = { [lang: string]: string; }`
454
455
  - `inpostAdapter(opts: InpostAdapterOptions): CarrierAdapter`
455
456
  - `interface InpostAdapterOptions`
@@ -480,6 +481,7 @@ Tags:
480
481
  - `interface PaymentRefundResult`
481
482
  - `payuAdapter(opts: PayuAdapterOptions): PaymentAdapter`
482
483
  - `interface PayuAdapterOptions`
484
+ - `renderShopName(template: I18nTemplate | null | undefined, ctx: { entryData: Record<string, unknown> | null | undefi..., locale: string): string | null` — Render a multilingual shop-name template (`invoiceName` / `cartName`)
483
485
  - `interface ResolvedShopConfig`
484
486
  - `interface ShipmentCreateInput`
485
487
  - `interface ShipmentCreateResult`
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.36.0 — 2026-06-05
7
+
8
+ `defineShop({ invoiceName, cartName })` — opcjonalne multilingual template overriding produktowej nazwy w koszyku, podsumowaniu zamówienia i na fakturze. Reusuje engine od `variantLabel.template` (`{slug}`, `{slug|filter}`), z nowymi możliwościami: dot-path (`{hero.title}`) i auto-unwrap pól i18n. Reserved `{variant}` = wyrenderowany variantLabel. Order item `nameSnapshot` zyskuje opcjonalne pole `invoice` (zamrażane przy checkout). Additive — brak zmian default behavior.
9
+
10
+ ### Added
11
+ - `ShopConfig.invoiceName?: I18nText` i `ShopConfig.cartName?: I18nText` — template (per język) renderowany przy hydracji koszyka i tworzeniu zamówienia. Fallback chain: `cartName` (koszyk) → `invoiceName` (koszyk + faktura) → legacy `productTitle` / `product — variant`. `invoiceName` jest **full override** na fakturze — wariant NIE jest doklejany automatycznie, użyj `{variant}` w template.
12
+ - `interpolateTemplate()` (`$lib/shop/template.ts`) zyskuje dot-path resolver (`{hero.title}`) oraz auto-unwrap pól i18n `{ pl: …, en: … }` do aktualnego języka (z fallbackiem `lang.split("-")[0]` → pierwszy klucz). Backwards-compatible: istniejące `{key}` (płaskie) działają bez zmian.
13
+ - `renderShopName(template, ctx, locale)` (`$lib/shop/template.ts`) — public helper łączący resolverę I18nText template + budowę vars `{ ...entryData, variant }`. Używany wewnętrznie przez cart-hydrate; eksportowany dla customowych use case (np. own checkout flows).
14
+ - `CartLine` zyskuje pole `invoiceTitle: string | null` — frozen invoice copy obok `productTitle` (cart copy). `orders.createOrderFromCart` snapshot-uje to do `nameSnapshot.invoice`. `buildInvoicePayload()` honoruje `nameSnapshot.invoice` — fallback do `product — variant` gdy brak.
15
+
6
16
  ## 0.35.0 — 2026-06-05
7
17
 
8
18
  Kupon `appliesTo` (netto/brutto), uczestnicy w `notes` (admin + email), Calendar+Popover w polach `date`/`datetime` (admin), `api.ts` bez `FormEntryMap` gdy projekt nie ma form. Additive only — domyślne zachowania bez zmian.
package/DOCS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Includio CMS Documentation (v0.35.0)
1
+ # Includio CMS Documentation (v0.36.0)
2
2
 
3
3
  > This file is auto-generated from the docs site. For the latest version, update the package.
4
4
 
@@ -9,6 +9,13 @@ export interface CartLine extends CartItemRef {
9
9
  variantName: Record<string, string> | null;
10
10
  variantSku: string | null;
11
11
  productTitle: string | null;
12
+ /**
13
+ * Frozen invoice-line name rendered from `ShopConfig.invoiceName` at hydrate
14
+ * time. Null when `invoiceName` is not configured. Carried separately from
15
+ * `productTitle` so order creation can snapshot the cart copy and the
16
+ * invoice copy independently.
17
+ */
18
+ invoiceTitle: string | null;
12
19
  productSlug: string | null;
13
20
  priceNet: number;
14
21
  priceGross: number;
@@ -13,4 +13,5 @@ export { fakturowniaAdapter } from './adapters/fakturownia/index.js';
13
13
  export type { FakturowniaAdapterOptions } from './adapters/fakturownia/index.js';
14
14
  export { isValidNip } from './nip.js';
15
15
  export type { ShopConfig, ResolvedShopConfig, Currency, Order, OrderStatus, PaymentAdapter, PaymentCreateContext, PaymentRefundInput, PaymentRefundResult, CarrierAdapter, CarrierEvent, ShipmentCreateInput, ShipmentCreateResult, ShipmentLabel, ConsentConfig, ShopFeatures, PaymentCreateResult, PaymentEvent, OrderRef, CouponRef, I18nText, VariantAttribute, VariantAttributeText, VariantAttributeNumber, VariantAttributeDatetime, VariantAttributeSelect, VariantAttributeBoolean, VariantAttributeImage, VariantAttributeEntry, VariantAttributeSlug, VariantLabelConfig, VariantExpiryConfig, PaymentPolicy, DepositAmount, PartialPayment, InvoicingAdapter, InvoiceIssuePolicy, InvoiceBuyer, InvoiceLineItem, InvoicePayload, InvoiceCreateResult, InvoiceContext } from './types.js';
16
- export { interpolateTemplate } from './template.js';
16
+ export { interpolateTemplate, renderShopName } from './template.js';
17
+ export type { I18nTemplate } from './template.js';
@@ -17,7 +17,9 @@ export function defineShop(config) {
17
17
  orderViewUrl: config.orderViewUrl ?? '/shop/order/{orderNumber}?token={accessToken}',
18
18
  variantAttributes: config.variantAttributes ?? {},
19
19
  variantLabel: config.variantLabel ?? null,
20
- variantExpiry: config.variantExpiry ?? null
20
+ variantExpiry: config.variantExpiry ?? null,
21
+ invoiceName: config.invoiceName ?? null,
22
+ cartName: config.cartName ?? null
21
23
  };
22
24
  }
23
25
  export { InvalidVariantAttributesError } from './variant-attributes.js';
@@ -28,4 +30,4 @@ export { stripeAdapter } from './adapters/stripe/index.js';
28
30
  export { inpostAdapter } from './adapters/inpost/index.js';
29
31
  export { fakturowniaAdapter } from './adapters/fakturownia/index.js';
30
32
  export { isValidNip } from './nip.js';
31
- export { interpolateTemplate } from './template.js';
33
+ export { interpolateTemplate, renderShopName } from './template.js';
@@ -1,4 +1,4 @@
1
- import { eq, inArray } from 'drizzle-orm';
1
+ import { desc, inArray } from 'drizzle-orm';
2
2
  import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
3
3
  import { entriesTable } from '../../db-postgres/schema/entry.js';
4
4
  import { entryVersionsTable } from '../../db-postgres/schema/entryVersion.js';
@@ -6,6 +6,7 @@ import { getCMS } from '../../core/cms.js';
6
6
  import { getShopDb, requireShopConfig } from './db.js';
7
7
  import { grossFromNet, grossPlnFromNetPln, toCents } from '../pricing.js';
8
8
  import { buildAppliedCoupon, validateCoupon, CouponError } from './coupons.js';
9
+ import { renderShopName } from '../template.js';
9
10
  export async function hydrateCart(items, opts = {}) {
10
11
  const shop = requireShopConfig();
11
12
  const cms = getCMS();
@@ -42,10 +43,16 @@ export async function hydrateCart(items, opts = {}) {
42
43
  .select()
43
44
  .from(entryVersionsTable)
44
45
  .where(inArray(entryVersionsTable.entryId, entryIds))
46
+ .orderBy(desc(entryVersionsTable.versionNumber))
45
47
  : [];
46
48
  const publishedByEntry = new Map();
49
+ const now = new Date();
47
50
  for (const v of versions) {
48
- if (v.publishedAt != null)
51
+ if (v.lang !== language)
52
+ continue;
53
+ if (v.publishedAt == null || v.publishedAt > now)
54
+ continue;
55
+ if (!publishedByEntry.has(v.entryId))
49
56
  publishedByEntry.set(v.entryId, v);
50
57
  }
51
58
  const productById = new Map(products.map((p) => [p.id, p]));
@@ -62,6 +69,19 @@ export async function hydrateCart(items, opts = {}) {
62
69
  }
63
70
  return null;
64
71
  }
72
+ function readVariantLabel(name, lang) {
73
+ if (!name || typeof name !== 'object')
74
+ return '';
75
+ const map = name;
76
+ const direct = map[lang];
77
+ if (typeof direct === 'string' && direct.length > 0)
78
+ return direct;
79
+ const base = map[lang.split('-')[0]];
80
+ if (typeof base === 'string' && base.length > 0)
81
+ return base;
82
+ const first = Object.values(map).find((v) => typeof v === 'string' && v.length > 0);
83
+ return first ?? '';
84
+ }
65
85
  function readSlug(data) {
66
86
  if (!data || typeof data !== 'object')
67
87
  return null;
@@ -95,6 +115,11 @@ export async function hydrateCart(items, opts = {}) {
95
115
  const version = publishedByEntry.get(product.entryId);
96
116
  const title = readTitle(version?.data);
97
117
  const slug = readSlug(version?.data);
118
+ const entryData = (version?.data ?? null);
119
+ const variantLabel = readVariantLabel(variant.name, language);
120
+ const renderedInvoice = renderShopName(shop.invoiceName, { entryData, variant: variantLabel, language }, language);
121
+ const renderedCart = renderShopName(shop.cartName, { entryData, variant: variantLabel, language }, language) ?? renderedInvoice;
122
+ const cartTitle = renderedCart ?? title;
98
123
  // basePrice/priceDelta są numeric(20,6) → drizzle zwraca string. Konwertujemy do PLN (number).
99
124
  const priceNetPln = Number(product.basePrice) + Number(variant.priceDelta ?? 0);
100
125
  const priceGrossPln = grossPlnFromNetPln(priceNetPln, product.vatRate);
@@ -130,7 +155,8 @@ export async function hydrateCart(items, opts = {}) {
130
155
  productId: product.id,
131
156
  variantName: variant.name ?? null,
132
157
  variantSku: variant.sku,
133
- productTitle: title,
158
+ productTitle: cartTitle,
159
+ invoiceTitle: renderedInvoice,
134
160
  productSlug: slug,
135
161
  priceNet,
136
162
  priceGross,
@@ -210,6 +236,7 @@ export async function hydrateCart(items, opts = {}) {
210
236
  variantName: null,
211
237
  variantSku: null,
212
238
  productTitle: null,
239
+ invoiceTitle: null,
213
240
  productSlug: null,
214
241
  priceNet: 0,
215
242
  priceGross: 0,
@@ -1,5 +1,4 @@
1
1
  import { getCMS } from '../../core/cms.js';
2
- import { resolveI18n } from '../pricing.js';
3
2
  import { getOrderById, getOrderItems } from './orders.js';
4
3
  import { getOrderCoupon } from './coupons.js';
5
4
  import { requireShopConfig } from './db.js';
@@ -151,13 +150,18 @@ export async function sendOrderStatusEmail(orderId, status) {
151
150
  lastName: p.lastName
152
151
  })),
153
152
  hasParticipants: participants.length > 0,
154
- items: items.map((i) => ({
155
- name: resolveI18n(i.nameSnapshot?.product
156
- ? { pl: i.nameSnapshot.product }
157
- : undefined, lang) || '—',
158
- qty: i.qty,
159
- lineGross: formatPrice(i.priceGrossSnapshot * i.qty, order.currency)
160
- })),
153
+ items: items.map((i) => {
154
+ const snap = (i.nameSnapshot ?? {});
155
+ const composed = snap.invoice ||
156
+ (snap.product && snap.variant
157
+ ? `${snap.product} — ${snap.variant}`
158
+ : snap.product || snap.variant || '');
159
+ return {
160
+ name: composed || '—',
161
+ qty: i.qty,
162
+ lineGross: formatPrice(i.priceGrossSnapshot * i.qty, order.currency)
163
+ };
164
+ }),
161
165
  shop: {
162
166
  currency: order.currency,
163
167
  adminEmail: shop.adminEmail ?? null
@@ -43,10 +43,16 @@ export function decideInvoiceAction(order, existing, policy, opts = {}) {
43
43
  return 'create';
44
44
  return 'skip';
45
45
  }
46
- // Order-item `nameSnapshot` is `{ product, variant }` (see createOrderFromCart).
47
- // The invoice line shows both so the customer sees the exact session they paid
48
- // for (e.g. "Odporność psychiczna Poznań 20 września 2026").
46
+ // Order-item `nameSnapshot` is `{ product, variant, invoice? }` (see
47
+ // createOrderFromCart). When `invoice` is present it was rendered from
48
+ // `ShopConfig.invoiceName` at order time and is treated as a full override —
49
+ // the variant suffix is NOT appended (template author controls whether to
50
+ // include `{variant}`). Otherwise the legacy `product — variant` format is
51
+ // used (e.g. "Odporność psychiczna — Poznań • 20 września 2026").
49
52
  function lineName(name) {
53
+ const invoice = name.invoice ?? '';
54
+ if (invoice)
55
+ return invoice;
50
56
  const product = name.product ?? '';
51
57
  const variant = name.variant ?? '';
52
58
  if (product && variant)
@@ -327,7 +327,8 @@ export async function createOrderFromCart(input) {
327
327
  product: line.productTitle ?? '',
328
328
  variant: line.variantName && typeof line.variantName === 'object'
329
329
  ? (Object.values(line.variantName)[0] ?? '')
330
- : ''
330
+ : '',
331
+ ...(line.invoiceTitle ? { invoice: line.invoiceTitle } : {})
331
332
  },
332
333
  skuSnapshot: line.variantSku ?? null,
333
334
  priceNetSnapshot: line.priceNet,
@@ -11,3 +11,23 @@
11
11
  * @public
12
12
  */
13
13
  export declare function interpolateTemplate(template: string, vars: Record<string, unknown>, locale: string): string;
14
+ /**
15
+ * Render a multilingual shop-name template (`invoiceName` / `cartName`)
16
+ * against an entry's data and a precomputed variant label. Returns `null`
17
+ * when the template is absent or yields an empty string after trimming —
18
+ * callers fall back to legacy behavior.
19
+ *
20
+ * Variables: `{<slug>}` resolves to any top-level (dot-path) field on the
21
+ * entry's published data. The reserved `{variant}` returns the rendered
22
+ * variant label. I18n fields stored as `{ pl: '…', en: '…' }` are auto
23
+ * unwrapped to the active language.
24
+ *
25
+ * @public
26
+ */
27
+ export declare function renderShopName(template: I18nTemplate | null | undefined, ctx: {
28
+ entryData: Record<string, unknown> | null | undefined;
29
+ variant: string;
30
+ language: string;
31
+ }, locale: string): string | null;
32
+ /** @public */
33
+ export type I18nTemplate = Record<string, string>;
@@ -17,18 +17,95 @@ export function interpolateTemplate(template, vars, locale) {
17
17
  }
18
18
  return template.replace(/\{([^}]+)\}/g, (_match, body) => {
19
19
  const { key, filter, arg } = parsePlaceholder(body);
20
- if (!(key in vars)) {
20
+ const lookup = resolveKey(vars, key, locale);
21
+ if (lookup === undefined) {
21
22
  console.warn(`[interpolateTemplate] Unknown key "${key}" in template: ${template}`);
22
23
  return '';
23
24
  }
24
- const raw = vars[key];
25
- if (raw === null || raw === undefined)
25
+ if (lookup === null)
26
26
  return '';
27
27
  if (!filter)
28
- return String(raw);
29
- return applyFilter(raw, filter, arg, locale);
28
+ return String(lookup);
29
+ return applyFilter(lookup, filter, arg, locale);
30
30
  });
31
31
  }
32
+ /**
33
+ * Resolve a placeholder key against `vars`. Supports dot-path (`hero.title`)
34
+ * and auto-unwraps an `I18nText`-shaped record (`{ pl: '…', en: '…' }`) to
35
+ * the active locale (falls back to the first key when the exact locale is
36
+ * missing). Returns `undefined` when the path does not exist — caller treats
37
+ * that as "unknown key" and warns. Returns `null` when the path exists but
38
+ * the value is nullish; caller emits empty string.
39
+ *
40
+ * @internal
41
+ */
42
+ function resolveKey(vars, key, locale) {
43
+ const parts = key.split('.');
44
+ let current = vars;
45
+ for (const part of parts) {
46
+ if (current === null || current === undefined)
47
+ return undefined;
48
+ if (typeof current !== 'object')
49
+ return undefined;
50
+ const obj = current;
51
+ if (!(part in obj))
52
+ return undefined;
53
+ current = obj[part];
54
+ }
55
+ return unwrapI18n(current, locale);
56
+ }
57
+ /** @internal */
58
+ function unwrapI18n(value, locale) {
59
+ if (value === null || value === undefined)
60
+ return value;
61
+ if (typeof value !== 'object' || Array.isArray(value))
62
+ return value;
63
+ const obj = value;
64
+ const keys = Object.keys(obj);
65
+ if (keys.length === 0)
66
+ return value;
67
+ const allStrings = keys.every((k) => typeof obj[k] === 'string');
68
+ if (!allStrings)
69
+ return value;
70
+ if (locale in obj)
71
+ return obj[locale];
72
+ const baseLocale = locale.split('-')[0];
73
+ if (baseLocale in obj)
74
+ return obj[baseLocale];
75
+ return obj[keys[0]];
76
+ }
77
+ /**
78
+ * Render a multilingual shop-name template (`invoiceName` / `cartName`)
79
+ * against an entry's data and a precomputed variant label. Returns `null`
80
+ * when the template is absent or yields an empty string after trimming —
81
+ * callers fall back to legacy behavior.
82
+ *
83
+ * Variables: `{<slug>}` resolves to any top-level (dot-path) field on the
84
+ * entry's published data. The reserved `{variant}` returns the rendered
85
+ * variant label. I18n fields stored as `{ pl: '…', en: '…' }` are auto
86
+ * unwrapped to the active language.
87
+ *
88
+ * @public
89
+ */
90
+ export function renderShopName(template, ctx, locale) {
91
+ if (!template)
92
+ return null;
93
+ const tpl = template[ctx.language] ?? template[ctx.language.split('-')[0]] ?? null;
94
+ if (!tpl)
95
+ return null;
96
+ const vars = {
97
+ ...(ctx.entryData ?? {}),
98
+ variant: ctx.variant
99
+ };
100
+ for (const match of tpl.matchAll(/\{([^}]+)\}/g)) {
101
+ const key = match[1].split('|')[0].trim();
102
+ const value = resolveKey(vars, key, locale);
103
+ if (value === undefined || value === null || value === '')
104
+ return null;
105
+ }
106
+ const rendered = interpolateTemplate(tpl, vars, locale).trim();
107
+ return rendered.length > 0 ? rendered : null;
108
+ }
32
109
  /** @internal */
33
110
  function parsePlaceholder(body) {
34
111
  const [keyPart, filterPart] = body.split('|', 2);
@@ -394,6 +394,28 @@ export interface ShopConfig {
394
394
  * @public
395
395
  */
396
396
  variantLabel?: VariantLabelConfig;
397
+ /**
398
+ * Multilingual template overriding the product name shown on invoices
399
+ * (Fakturownia etc.) and — unless `cartName` is set — also in cart and
400
+ * order views. Keys are language codes (`pl`, `en`, …). Placeholders use
401
+ * the same engine as `variantLabel`: `{slug}` resolves to a top-level
402
+ * field on the entry (dot-path supported, i18n-aware); the reserved
403
+ * `{variant}` resolves to the rendered `variantLabel`. Filters from
404
+ * {@link interpolateTemplate} (`|date`, `|currency`, `|uppercase`) apply.
405
+ * Omitted = legacy behavior (`product — variant`).
406
+ * @public
407
+ */
408
+ invoiceName?: I18nText;
409
+ /**
410
+ * Multilingual template overriding the product name shown in cart and
411
+ * storefront order views ONLY (invoice still uses `invoiceName` or
412
+ * legacy). Set this in addition to `invoiceName` when the cart needs a
413
+ * shorter / different copy than the invoice line. Same syntax as
414
+ * {@link ShopConfig.invoiceName}. Omitted = falls back to `invoiceName`,
415
+ * then to legacy `productTitle`.
416
+ * @public
417
+ */
418
+ cartName?: I18nText;
397
419
  /**
398
420
  * Opt-in storefront filter for time-bound variants (events, courses).
399
421
  * Omitted = no filtering (legacy behavior — every variant always listed).
@@ -434,7 +456,7 @@ export interface ShopConfig {
434
456
  */
435
457
  onOrderPaid?: (order: Order) => Promise<void> | void;
436
458
  }
437
- export interface ResolvedShopConfig extends Omit<ShopConfig, 'variantLabel' | 'variantExpiry' | 'invoicing'> {
459
+ export interface ResolvedShopConfig extends Omit<ShopConfig, 'variantLabel' | 'variantExpiry' | 'invoicing' | 'invoiceName' | 'cartName'> {
438
460
  features: Required<ShopFeatures>;
439
461
  rateLimit: Required<ShopRateLimit>;
440
462
  carriers: CarrierAdapter[];
@@ -444,5 +466,7 @@ export interface ResolvedShopConfig extends Omit<ShopConfig, 'variantLabel' | 'v
444
466
  variantAttributes: Record<string, VariantAttribute>;
445
467
  variantLabel: VariantLabelConfig | null;
446
468
  variantExpiry: VariantExpiryConfig | null;
469
+ invoiceName: I18nText | null;
470
+ cartName: I18nText | null;
447
471
  }
448
472
  export {};
@@ -0,0 +1,2 @@
1
+ import type { CmsUpdate } from '../index.js';
2
+ export declare const update: CmsUpdate;
@@ -0,0 +1,13 @@
1
+ export const update = {
2
+ version: '0.36.0',
3
+ date: '2026-06-05',
4
+ description: '`defineShop({ invoiceName, cartName })` — opcjonalne multilingual template overriding produktowej nazwy w koszyku, podsumowaniu zamówienia i na fakturze. Reusuje engine od `variantLabel.template` (`{slug}`, `{slug|filter}`), z nowymi możliwościami: dot-path (`{hero.title}`) i auto-unwrap pól i18n. Reserved `{variant}` = wyrenderowany variantLabel. Order item `nameSnapshot` zyskuje opcjonalne pole `invoice` (zamrażane przy checkout). Additive — brak zmian default behavior.',
5
+ features: [
6
+ '`ShopConfig.invoiceName?: I18nText` i `ShopConfig.cartName?: I18nText` — template (per język) renderowany przy hydracji koszyka i tworzeniu zamówienia. Fallback chain: `cartName` (koszyk) → `invoiceName` (koszyk + faktura) → legacy `productTitle` / `product — variant`. `invoiceName` jest **full override** na fakturze — wariant NIE jest doklejany automatycznie, użyj `{variant}` w template.',
7
+ '`interpolateTemplate()` (`$lib/shop/template.ts`) zyskuje dot-path resolver (`{hero.title}`) oraz auto-unwrap pól i18n `{ pl: …, en: … }` do aktualnego języka (z fallbackiem `lang.split("-")[0]` → pierwszy klucz). Backwards-compatible: istniejące `{key}` (płaskie) działają bez zmian.',
8
+ '`renderShopName(template, ctx, locale)` (`$lib/shop/template.ts`) — public helper łączący resolverę I18nText template + budowę vars `{ ...entryData, variant }`. Używany wewnętrznie przez cart-hydrate; eksportowany dla customowych use case (np. own checkout flows).',
9
+ '`CartLine` zyskuje pole `invoiceTitle: string | null` — frozen invoice copy obok `productTitle` (cart copy). `orders.createOrderFromCart` snapshot-uje to do `nameSnapshot.invoice`. `buildInvoicePayload()` honoruje `nameSnapshot.invoice` — fallback do `product — variant` gdy brak.'
10
+ ],
11
+ fixes: [],
12
+ breakingChanges: []
13
+ };
@@ -66,6 +66,7 @@ import { update as update0280 } from './0.28.0/index.js';
66
66
  import { update as update0340 } from './0.34.0/index.js';
67
67
  import { update as update0341 } from './0.34.1/index.js';
68
68
  import { update as update0350 } from './0.35.0/index.js';
69
+ import { update as update0360 } from './0.36.0/index.js';
69
70
  export const updates = [
70
71
  update0065,
71
72
  update0066,
@@ -134,7 +135,8 @@ export const updates = [
134
135
  update0280,
135
136
  update0340,
136
137
  update0341,
137
- update0350
138
+ update0350,
139
+ update0360
138
140
  ];
139
141
  export const getUpdatesFrom = (fromVersion) => {
140
142
  const fromParts = fromVersion.split('.').map(Number);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "includio-cms",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",