@ikas/code-components-mcp 1.4.0-beta.167 → 1.4.0-beta.168

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.
@@ -326,7 +326,7 @@
326
326
  "cart-patterns": {
327
327
  "title": "Cart Management Patterns (Serel Reference)",
328
328
  "description": "Serel CartPage section with CartItem sub-component, OrderSummary/CouponCode/EmptyState local components, cartStore patterns, and checkout flow",
329
- "content": "## Cart Management Patterns (Serel Reference)\n\nThe serel CartPage is a section that uses local internal components (OrderSummary, CouponCode, EmptyState) and the shared `CartItem` sub-component from `src/sub-components/CartItem/`. Unlike most sections, CartPage does NOT use IkasComponentRenderer — it directly composes its UI from sub-components.\n\n### CartPage Section Structure\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport CartItem from \"../../sub-components/CartItem\";\nimport { Props } from \"./types\";\n\nexport function CartPage({ backgroundColor, emptyCartText, ...props }: Props) {\n const cart = cartStore.cart;\n const lineItems = cart?.orderLineItems ?? [];\n const isEmpty = lineItems.length === 0;\n\n if (isEmpty) return <EmptyState text={emptyCartText} />;\n\n return (\n <section className=\"cart-page\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"cart-page-inner\">\n <div className=\"cart-items\">\n {lineItems.map((item) => (\n <CartItem key={item.id} item={item} />\n ))}\n </div>\n <div className=\"cart-sidebar\">\n <CouponCode cart={cart} />\n <OrderSummary cart={cart} />\n </div>\n </div>\n </section>\n );\n}\nexport default CartPage;\n```\n\n### CartItem Sub-Component\n\nThe `CartItem` sub-component (in `src/sub-components/CartItem/`) displays a single line item with image, variant name, quantity controls, price, and remove button:\n\n```tsx\nimport {\n getOrderLineItemFormattedFinalPrice,\n getOrderLineItemFormattedUnitPrice,\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n changeItemQuantity,\n removeItem,\n IkasOrderLineItem,\n} from \"@ikas/bp-storefront\";\nimport QuantitySelector from \"../QuantitySelector\";\n\ninterface Props { item: IkasOrderLineItem; }\n\nexport default function CartItem({ item }: Props) {\n const image = item.variant ? getIkasOrderLineVariantMainImage(item.variant) : null;\n\n return (\n <div className=\"cart-item\">\n {image && <img src={getDefaultSrc(image)} alt={item.variant?.name} />}\n <div className=\"cart-item-info\">\n <span>{item.variant?.name}</span>\n <span>{getOrderLineItemFormattedUnitPrice(item)}</span>\n </div>\n <QuantitySelector\n quantity={item.quantity}\n onIncrement={() => changeItemQuantity(item, item.quantity + 1)}\n onDecrement={() => changeItemQuantity(item, Math.max(1, item.quantity - 1))}\n />\n <span>{getOrderLineItemFormattedFinalPrice(item)}</span>\n <button onClick={() => removeItem(item)}>Remove</button>\n </div>\n );\n}\n```\n\n### OrderSummary Local Component\n\nDisplays cart totals using `getIkasOrderFormattedTotalFinalPrice(cart)` and `getIkasOrderFormattedTotalPrice(cart)`, plus a checkout button:\n\n```tsx\nimport { Router, getIkasOrderFormattedTotalFinalPrice } from \"@ikas/bp-storefront\";\n\nfunction OrderSummary({ cart }) {\n return (\n <div className=\"order-summary\">\n <span>Total: {getIkasOrderFormattedTotalFinalPrice(cart)}</span>\n <button onClick={() => Router.navigateToPage(\"CHECKOUT\")}>Checkout</button>\n </div>\n );\n}\n```\n\n### CouponCode Local Component\n\nCoupon code uses the standard form model. Get the form from `customerStore` via `getCouponCodeForm`, init it once, drive the input with `setCouponCodeFormCouponCode`, and submit/remove with the form helpers. Do NOT call `saveCart`/`apiSaveCart` or any deep `@ikas/bp-storefront/dist/...` import — the coupon has dedicated public exports:\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n getCouponCodeForm,\n initCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n removeCouponCodeForm,\n} from \"@ikas/bp-storefront\";\nimport { observer } from \"@ikas/component-utils\";\n\n// observer() because this sub-component reads the MobX coupon form\nconst CouponCode = observer(function CouponCode({ appliedCoupon }) {\n const couponForm = getCouponCodeForm(customerStore);\n\n useEffect(() => {\n initCouponCodeForm(couponForm);\n }, [couponForm]);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n await submitCouponCodeForm(couponForm);\n };\n\n if (appliedCoupon) {\n return (\n <div className=\"coupon\">\n <span>{appliedCoupon}</span>\n <button type=\"button\" onClick={() => removeCouponCodeForm(couponForm)}>Remove</button>\n </div>\n );\n }\n\n return (\n <form className=\"coupon\" onSubmit={handleSubmit}>\n <input\n placeholder={couponForm.couponCode?.placeholder ?? \"Coupon code\"}\n value={couponForm.couponCode?.value ?? \"\"}\n onInput={(e) =>\n setCouponCodeFormCouponCode(couponForm, (e.target as HTMLInputElement).value)\n }\n />\n <button type=\"submit\" disabled={couponForm.isSubmitting}>\n {couponForm.isSubmitting ? \"Applying...\" : \"Apply\"}\n </button>\n {couponForm.isFailure && couponForm.responseMessage && (\n <p className=\"coupon-error\">{couponForm.responseMessage}</p>\n )}\n </form>\n );\n});\n```\n\n### cartStore Usage Patterns\n\nThe `cartStore` is a MobX store available globally. Root components are automatically reactive via autorun, so reads from `cartStore.cart` trigger re-renders:\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Always null-safe:\nconst cart = cartStore.cart;\nconst lineItems = cart?.orderLineItems ?? [];\nconst itemCount = lineItems.length;\nconst isEmpty = itemCount === 0;\n```\n\n### Quantity Limits (min/max per cart)\n\nA product's sales channel can cap how many units go into one cart. The limits live on the product's `salesChannels`, matched to the active channel:\n\n```tsx\nimport { IkasStorefrontConfig } from \"@ikas/bp-storefront\";\n\nconst channel = product.salesChannels?.find((sc) => sc.id === IkasStorefrontConfig.salesChannelId);\nconst min = channel?.minQuantityPerCart ?? 1;\nconst max = channel?.maxQuantityPerCart; // null/undefined = no cap\n```\n\nApply these to the quantity stepper: seed the initial quantity to `min`, and cap increments at `max`. `addItemToCart(variant, product, qty)` clamps to the same limits internally only when it creates a **new** cart line — and silently, so enforce them in the UI too or the customer sees the number snap with no explanation.\n\n> **The internal clamp only runs when a NEW cart line is created.** If the variant already has a line in the cart, `addItemToCart` delegates to `changeItemQuantity(existing, existing.quantity + qty)` before the clamp is reached — and `changeItemQuantity` never clamps. Cart page / cart drawer steppers therefore get no SDK enforcement at all; enforce the limits in the UI (see \"Enforcing Limits on Cart Lines\" below).\n\n> `IkasStorefrontConfig.salesChannelId` is a property. The older `getSalesChannelId()` method does not exist in this SDK.\n\n### Cart Operation validationError (complete list)\n\n`addItemToCart` / `changeItemQuantity` return `IkasCartOperationResult` — `{ success, validationError?, response? }`. The `validationError` union has exactly three values, but at runtime only `addItemToCart` ever sets it (client-side pre-validation before the API call). `changeItemQuantity` does no client-side validation and never returns a `validationError` — branch on `success` (and `response.errorCodes` for details), not on `validationError`, for cart-line quantity changes:\n\n| Value | Meaning |\n|-------|---------|\n| `INSUFFICIENT_STOCK` | Not enough stock for the requested quantity |\n| `INVALID_PRODUCT_OPTION_VALUES` | Required product options not fully or validly selected |\n| `EMPTY_CART` | The operation needs a non-empty cart (declared in the type union, but no current code path emits it) |\n\nQuantity limits are never surfaced as a `validationError` — there is **no** `MAX_QUANTITY_PER_CART_*` / `MIN_QUANTITY_PER_CART_*` value beyond the three above. Note the distinction: legacy v1 themes branched on the GraphQL error code `MAX_QUANTITY_PER_CART_LIMIT_REACHED` (an `extensions.code` value, which the API client copies into `response.errorCodes` unfiltered), but that code is not a documented contract of this SDK — do not rely on it. Prevent limit violations in the stepper instead (see \"Quantity Limits\" above and \"Enforcing Limits on Cart Lines\" below).\n\n### Enforcing Limits on Cart Lines\n\nCart lines cannot tell you the limits: `IkasOrderLineItem.variant` is an `IkasOrderLineVariant`, which carries **neither** `salesChannels` nor `stock` — both live only on `IkasProduct` / `IkasProductVariant`. To enforce `minQuantityPerCart` / `maxQuantityPerCart` (or stock caps) on the cart page / drawer, batch-fetch the line products once with `bs_searchProductsById` and map the limits by product id:\n\n```tsx\nimport { baseStore, bs_searchProductsById, cartStore, IkasStorefrontConfig } from \"@ikas/bp-storefront\";\n\n// Collect the distinct productIds of the cart lines and fetch them in ONE request:\nconst productIds = [\n ...new Set(\n (cartStore.cart?.orderLineItems ?? [])\n .map((item) => item.variant.productId)\n .filter((id): id is string => !!id)\n ),\n];\nconst { products } = await bs_searchProductsById(baseStore, { productIds });\n\nconst limitsByProductId = new Map<string, { min: number; max: number | null }>();\nfor (const product of products ?? []) {\n const channel = product.salesChannels?.find((sc) => sc.id === IkasStorefrontConfig.salesChannelId);\n limitsByProductId.set(product.id, {\n min: channel?.minQuantityPerCart ?? 1,\n max: channel?.maxQuantityPerCart ?? null,\n });\n}\n\n// In the line's stepper:\nconst limits = limitsByProductId.get(item.variant.productId ?? \"\");\nconst atMax = limits?.max != null && item.quantity >= limits.max;\n// disable the + button when atMax; block decrements below limits.min the same way\n```\n\n`bs_searchProductsById` returns full `IkasProduct` objects, so `salesChannels` (already filtered to the active sales channel) and per-variant `stock` (summed over the sales channel's configured stock locations) come in the same response — one fetch covers both quantity limits and stock caps. Fetch once per cart open and cache by product id rather than fetching per line; when lines render independently, collect the ids over a tick and flush a single batched request.\n\n### Campaign Offer Lines in the Cart\n\nCampaign discounts surface as order-level adjustment rows, not as edits on the line items. Format the adjustment amount with `formatCurrency` — the root export that applies the merchant's currency format settings:\n\n```tsx\nimport { formatCurrency } from \"@ikas/bp-storefront\";\n\n{cart.orderAdjustments?.map((adj) => (\n <div key={adj.name} className=\"cart-adjustment\">\n <span>{adj.name}</span>\n <span>{formatCurrency(adj.amount, adj.currencyCode, adj.currencySymbol)}</span>\n </div>\n))}\n```\n\nWhen a campaign auto-adds a bundled or gift line (a \"buy 1 get N\" offer), `isOrderLineItemAutoCreated(cart, item)` returns `true` for that line. Use it to badge the line and hide its quantity/remove controls — the customer cannot edit an auto-created line directly:\n\n```tsx\nimport { isOrderLineItemAutoCreated } from \"@ikas/bp-storefront\";\n\n{lineItems.map((item) => (\n <CartItem key={item.id} item={item} readOnly={isOrderLineItemAutoCreated(cart, item)} />\n))}\n```\n\n### Key Pattern: Section Without IkasComponentRenderer\n\nCartPage demonstrates that not all sections need COMPONENT_LIST slots. Simple sections can directly compose sub-components and local components when the layout is fixed and does not need editor-level child component placement.",
329
+ "content": "## Cart Management Patterns (Serel Reference)\n\nThe serel CartPage is a section that uses local internal components (OrderSummary, CouponCode, EmptyState) and the shared `CartItem` sub-component from `src/sub-components/CartItem/`. Unlike most sections, CartPage does NOT use IkasComponentRenderer — it directly composes its UI from sub-components.\n\n### CartPage Section Structure\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport CartItem from \"../../sub-components/CartItem\";\nimport { Props } from \"./types\";\n\nexport function CartPage({ backgroundColor, emptyCartText, ...props }: Props) {\n const cart = cartStore.cart;\n const lineItems = cart?.orderLineItems ?? [];\n const isEmpty = lineItems.length === 0;\n\n if (isEmpty) return <EmptyState text={emptyCartText} />;\n\n return (\n <section className=\"cart-page\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"cart-page-inner\">\n <div className=\"cart-items\">\n {lineItems.map((item) => (\n <CartItem key={item.id} item={item} />\n ))}\n </div>\n <div className=\"cart-sidebar\">\n <CouponCode cart={cart} />\n <OrderSummary cart={cart} />\n </div>\n </div>\n </section>\n );\n}\nexport default CartPage;\n```\n\n### CartItem Sub-Component\n\nThe `CartItem` sub-component (in `src/sub-components/CartItem/`) displays a single line item with image, variant name, quantity controls, price, and remove button:\n\n```tsx\nimport {\n getOrderLineItemFormattedFinalPrice,\n getOrderLineItemFormattedUnitPrice,\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n changeItemQuantity,\n removeItem,\n IkasOrderLineItem,\n} from \"@ikas/bp-storefront\";\nimport QuantitySelector from \"../QuantitySelector\";\n\ninterface Props { item: IkasOrderLineItem; }\n\nexport default function CartItem({ item }: Props) {\n const image = item.variant ? getIkasOrderLineVariantMainImage(item.variant) : null;\n\n return (\n <div className=\"cart-item\">\n {image && <img src={getDefaultSrc(image)} alt={item.variant?.name} />}\n <div className=\"cart-item-info\">\n <span>{item.variant?.name}</span>\n <span>{getOrderLineItemFormattedUnitPrice(item)}</span>\n </div>\n <QuantitySelector\n quantity={item.quantity}\n onIncrement={() => changeItemQuantity(item, item.quantity + 1)}\n onDecrement={() => changeItemQuantity(item, Math.max(1, item.quantity - 1))}\n />\n <span>{getOrderLineItemFormattedFinalPrice(item)}</span>\n <button onClick={() => removeItem(item)}>Remove</button>\n </div>\n );\n}\n```\n\n### OrderSummary Local Component\n\nDisplays cart totals using `getIkasOrderFormattedTotalFinalPrice(cart)` and `getIkasOrderFormattedTotalPrice(cart)`, plus a checkout button:\n\n```tsx\nimport { Router, getIkasOrderFormattedTotalFinalPrice } from \"@ikas/bp-storefront\";\n\nfunction OrderSummary({ cart }) {\n return (\n <div className=\"order-summary\">\n <span>Total: {getIkasOrderFormattedTotalFinalPrice(cart)}</span>\n <button onClick={() => Router.navigateToPage(\"CHECKOUT\")}>Checkout</button>\n </div>\n );\n}\n```\n\n### CouponCode Local Component\n\nCoupon code uses the standard form model. Get the form from `customerStore` via `getCouponCodeForm`, init it once, drive the input with `setCouponCodeFormCouponCode`, and submit/remove with the form helpers. Do NOT call `saveCart`/`apiSaveCart` or any deep `@ikas/bp-storefront/dist/...` import — the coupon has dedicated public exports:\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n getCouponCodeForm,\n initCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n removeCouponCodeForm,\n} from \"@ikas/bp-storefront\";\nimport { observer } from \"@ikas/component-utils\";\n\n// observer() because this sub-component reads the MobX coupon form\nconst CouponCode = observer(function CouponCode({ appliedCoupon }) {\n const couponForm = getCouponCodeForm(customerStore);\n\n useEffect(() => {\n initCouponCodeForm(couponForm);\n }, [couponForm]);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n await submitCouponCodeForm(couponForm);\n };\n\n if (appliedCoupon) {\n return (\n <div className=\"coupon\">\n <span>{appliedCoupon}</span>\n <button type=\"button\" onClick={() => removeCouponCodeForm(couponForm)}>Remove</button>\n </div>\n );\n }\n\n return (\n <form className=\"coupon\" onSubmit={handleSubmit}>\n <input\n placeholder={couponForm.couponCode?.placeholder ?? \"Coupon code\"}\n value={couponForm.couponCode?.value ?? \"\"}\n onInput={(e) =>\n setCouponCodeFormCouponCode(couponForm, (e.target as HTMLInputElement).value)\n }\n />\n <button type=\"submit\" disabled={couponForm.isSubmitting}>\n {couponForm.isSubmitting ? \"Applying...\" : \"Apply\"}\n </button>\n {couponForm.isFailure && couponForm.responseMessage && (\n <p className=\"coupon-error\">{couponForm.responseMessage}</p>\n )}\n </form>\n );\n});\n```\n\n### cartStore Usage Patterns\n\nThe `cartStore` is a MobX store available globally. Root components are automatically reactive via autorun, so reads from `cartStore.cart` trigger re-renders:\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Always null-safe:\nconst cart = cartStore.cart;\nconst lineItems = cart?.orderLineItems ?? [];\nconst itemCount = lineItems.length;\nconst isEmpty = itemCount === 0;\n```\n\n### Quantity Limits (min/max per cart)\n\nA product's sales channel can cap how many units go into one cart. The limits live on the product's `salesChannels`, matched to the active channel:\n\n```tsx\nimport { IkasStorefrontConfig } from \"@ikas/bp-storefront\";\n\nconst channel = product.salesChannels?.find((sc) => sc.id === IkasStorefrontConfig.salesChannelId);\nconst min = channel?.minQuantityPerCart ?? 1;\nconst max = channel?.maxQuantityPerCart; // null/undefined = no cap\n```\n\nApply these to the quantity stepper: seed the initial quantity to `min`, and cap increments at `max`. `addItemToCart(variant, product, qty)` clamps to the same limits internally only when it creates a **new** cart line — and silently, so enforce them in the UI too or the customer sees the number snap with no explanation.\n\n> **The internal clamp only runs when a NEW cart line is created.** If the variant already has a line in the cart, `addItemToCart` delegates to `changeItemQuantity(existing, existing.quantity + qty)` before the clamp is reached — and `changeItemQuantity` never clamps. Cart page / cart drawer steppers therefore get no SDK enforcement at all; enforce the limits in the UI (see \"Enforcing Limits on Cart Lines\" below).\n\n> `IkasStorefrontConfig.salesChannelId` is a property. The older `getSalesChannelId()` method does not exist in this SDK.\n\n### Cart Operation validationError (complete list)\n\n`addItemToCart` / `changeItemQuantity` return `IkasCartOperationResult` — `{ success, validationError?, response? }`. The `validationError` union has exactly three values, but at runtime only `addItemToCart` ever sets it (client-side pre-validation before the API call). `changeItemQuantity` does no client-side validation and never returns a `validationError` — branch on `success` (and `response.errorCodes` for details), not on `validationError`, for cart-line quantity changes:\n\n| Value | Meaning |\n|-------|---------|\n| `INSUFFICIENT_STOCK` | Not enough stock for the requested quantity |\n| `INVALID_PRODUCT_OPTION_VALUES` | Required product options not fully or validly selected |\n| `EMPTY_CART` | The operation needs a non-empty cart (declared in the type union, but no current code path emits it) |\n\nQuantity limits are never surfaced as a `validationError` — there is **no** `MAX_QUANTITY_PER_CART_*` / `MIN_QUANTITY_PER_CART_*` value beyond the three above. They surface one level lower instead: when a mutation would push a line past `maxQuantityPerCart`, the API rejects it with the GraphQL error code `MAX_QUANTITY_PER_CART_LIMIT_REACHED` (an `extensions.code` value the API client copies into `response.errorCodes`). `addItemToCart` and `changeItemQuantity` go through the same mutation, so this is the reactive signal on the cart-line path — exactly where the SDK never clamps:\n\n```tsx\nimport { APIErrorCode, changeItemQuantity } from \"@ikas/bp-storefront\";\n\nconst result = await changeItemQuantity(item, qty);\nif (!result.success && result.response?.errorCodes?.includes(APIErrorCode.MAX_QUANTITY_PER_CART_LIMIT_REACHED)) {\n // The sales channel's per-cart cap rejected the quantity — tell the customer.\n}\n```\n\nTreat the error code as the fallback, not the UX: prevent limit violations proactively in the stepper (see \"Quantity Limits\" above and \"Enforcing Limits on Cart Lines\" below) so the customer never hits the rejection in the first place.\n\n### Enforcing Limits on Cart Lines\n\nCart lines cannot tell you the limits: `IkasOrderLineItem.variant` is an `IkasOrderLineVariant`, which carries **neither** `salesChannels` nor `stock` — both live only on `IkasProduct` / `IkasProductVariant`. To enforce `minQuantityPerCart` / `maxQuantityPerCart` (or stock caps) on the cart page / drawer, batch-fetch the line products once with `bs_searchProductsById` and map the limits by product id:\n\n```tsx\nimport { baseStore, bs_searchProductsById, cartStore, IkasStorefrontConfig } from \"@ikas/bp-storefront\";\n\n// Collect the distinct productIds of the cart lines and fetch them in ONE request:\nconst productIds = [\n ...new Set(\n (cartStore.cart?.orderLineItems ?? [])\n .map((item) => item.variant.productId)\n .filter((id): id is string => !!id)\n ),\n];\nconst { products } = await bs_searchProductsById(baseStore, { productIds });\n\nconst limitsByProductId = new Map<string, { min: number; max: number | null }>();\nfor (const product of products ?? []) {\n const channel = product.salesChannels?.find((sc) => sc.id === IkasStorefrontConfig.salesChannelId);\n limitsByProductId.set(product.id, {\n min: channel?.minQuantityPerCart ?? 1,\n max: channel?.maxQuantityPerCart ?? null,\n });\n}\n\n// In the line's stepper:\nconst limits = limitsByProductId.get(item.variant.productId ?? \"\");\nconst atMax = limits?.max != null && item.quantity >= limits.max;\n// disable the + button when atMax; block decrements below limits.min the same way\n```\n\n`bs_searchProductsById` returns full `IkasProduct` objects, so `salesChannels` (already filtered to the active sales channel) and per-variant `stock` (summed over the sales channel's configured stock locations) come in the same response — one fetch covers both quantity limits and stock caps. Fetch once per cart open and cache by product id rather than fetching per line; when lines render independently, collect the ids over a tick and flush a single batched request.\n\n### Campaign Offer Lines in the Cart\n\nCampaign discounts surface as order-level adjustment rows, not as edits on the line items. Format the adjustment amount with `formatCurrency` — the root export that applies the merchant's currency format settings:\n\n```tsx\nimport { formatCurrency } from \"@ikas/bp-storefront\";\n\n{cart.orderAdjustments?.map((adj) => (\n <div key={adj.name} className=\"cart-adjustment\">\n <span>{adj.name}</span>\n <span>{formatCurrency(adj.amount, adj.currencyCode, adj.currencySymbol)}</span>\n </div>\n))}\n```\n\nWhen a campaign auto-adds a bundled or gift line (a \"buy 1 get N\" offer), `isOrderLineItemAutoCreated(cart, item)` returns `true` for that line. Use it to badge the line and hide its quantity/remove controls — the customer cannot edit an auto-created line directly:\n\n```tsx\nimport { isOrderLineItemAutoCreated } from \"@ikas/bp-storefront\";\n\n{lineItems.map((item) => (\n <CartItem key={item.id} item={item} readOnly={isOrderLineItemAutoCreated(cart, item)} />\n))}\n```\n\n### Key Pattern: Section Without IkasComponentRenderer\n\nCartPage demonstrates that not all sections need COMPONENT_LIST slots. Simple sections can directly compose sub-components and local components when the layout is fixed and does not need editor-level child component placement.",
330
330
  "tags": [
331
331
  "cart",
332
332
  "checkout",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-19T12:18:27.436Z",
2
+ "generatedAt": "2026-08-19T12:35:16.476Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "apiListProductBrand",
@@ -12756,7 +12756,7 @@
12756
12756
  "name": "changeItemQuantity",
12757
12757
  "displayName": "changeQuantityOnCart",
12758
12758
  "signature": "async function changeItemQuantity(item: IkasOrderLineItem, quantity: number, offers?: IkasProductOffer[], product?: IkasProduct): Promise<IkasCartOperationResult>",
12759
- "description": "Change the quantity of an item in the cart.\nUse quantity=0 to remove the item.",
12759
+ "description": "Change the quantity of an item in the cart.\nUse quantity=0 to remove the item.\n\nPerforms no client-side clamping or validation: a quantity above the sales channel's\n`maxQuantityPerCart` is rejected by the API with the `MAX_QUANTITY_PER_CART_LIMIT_REACHED`\ncode in `result.response.errorCodes` (never as a `validationError`). Prevent violations in\nthe stepper and treat the error code as the fallback.",
12760
12760
  "params": [
12761
12761
  {
12762
12762
  "name": "item",
@@ -12777,7 +12777,7 @@
12777
12777
  ],
12778
12778
  "returns": "Result with success flag",
12779
12779
  "returnType": "Promise<IkasCartOperationResult>",
12780
- "example": "```typescript\nimport { cartStore, changeItemQuantity } from \"@ikas/bp-storefront\";\n\n// Increment quantity\nasync function handleIncrement(itemId: string) {\n const item = cartStore.cart?.orderLineItems.find(i => i.id === itemId);\n if (item) {\n const result = await changeItemQuantity(item, item.quantity + 1);\n if (!result.success) {\n showToast(\"Failed to update quantity\");\n }\n }\n}\n\n// Decrement quantity\nasync function handleDecrement(itemId: string) {\n const item = cartStore.cart?.orderLineItems.find(i => i.id === itemId);\n if (item && item.quantity > 1) {\n await changeItemQuantity(item, item.quantity - 1);\n }\n}\n```",
12780
+ "example": "```typescript\nimport { APIErrorCode, cartStore, changeItemQuantity } from \"@ikas/bp-storefront\";\n\n// Increment quantity\nasync function handleIncrement(itemId: string) {\n const item = cartStore.cart?.orderLineItems.find(i => i.id === itemId);\n if (item) {\n const result = await changeItemQuantity(item, item.quantity + 1);\n if (!result.success) {\n const limitHit = result.response?.errorCodes?.includes(\n APIErrorCode.MAX_QUANTITY_PER_CART_LIMIT_REACHED\n );\n showToast(limitHit ? \"Per-cart quantity limit reached\" : \"Failed to update quantity\");\n }\n }\n}\n\n// Decrement quantity\nasync function handleDecrement(itemId: string) {\n const item = cartStore.cart?.orderLineItems.find(i => i.id === itemId);\n if (item && item.quantity > 1) {\n await changeItemQuantity(item, item.quantity - 1);\n }\n}\n```",
12781
12781
  "categories": [
12782
12782
  "Cart"
12783
12783
  ],
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-19T12:18:27.461Z",
2
+ "generatedAt": "2026-08-19T12:35:16.642Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "TextElementTagTypes",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikas/code-components-mcp",
3
- "version": "1.4.0-beta.167",
3
+ "version": "1.4.0-beta.168",
4
4
  "description": "MCP server for ikas code components documentation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",