@ikas/code-components-mcp 2.4.1 → 2.4.2

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.
@@ -142,7 +142,7 @@
142
142
  "imports": {
143
143
  "title": "Import Patterns",
144
144
  "description": "How to import storefront functions and types",
145
- "content": "### Storefront Functions & Types\nImport API functions and TypeScript types from `@ikas/bp-storefront`:\n```typescript\nimport {\n // Product functions\n getSelectedProductVariant,\n getDisplayedProductVariantTypes,\n selectVariantValue,\n hasProductVariantStock,\n getProductVariantFormattedFinalPrice,\n \n // Cart functions\n addItemToCart,\n cartStore,\n \n // Navigation\n Router,\n \n // Customer\n customerStore,\n customerLogin,\n hasCustomer,\n \n // Favorites\n addIkasProductToFavorites,\n isFavoriteIkasProduct,\n\n // Type models\n IkasProduct,\n IkasProductVariant,\n IkasCart,\n IkasOrderLineItem,\n IkasCustomer,\n IkasOrder,\n IkasCategory,\n IkasBrand,\n IkasBlog\n} from \"@ikas/bp-storefront\";\n```\n\n### Store Instances\nPre-initialized MobX stores:\n```typescript\nimport { cartStore, customerStore } from \"@ikas/bp-storefront\";\n\n// Cart data\ncartStore.cart?.orderLineItems\n\n// Customer data \ncustomerStore.customer?.email\n```\n\n### Observer (for sub-components with reactive updates)\n```typescript\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span>Cart: {itemCount}</span>;\n});\n\n// Root export: plain function — ikas runtime handles reactivity via autorun()\nexport function MySection({ title }: Props) {\n return (\n <div>\n <h1>{title}</h1>\n <CartBadge />\n </div>\n );\n}\n\nexport default MySection;\n```\n\nDo **NOT** wrap root component exports with `observer()`. The ikas runtime handles root reactivity automatically via `autorun()`. Only wrap **sub-components** with `observer()` when they independently read from stores (`cartStore`, `customerStore`).",
145
+ "content": "### Storefront Functions & Types\nImport API functions and TypeScript types from `@ikas/bp-storefront`:\n```typescript\nimport {\n // Product functions\n getSelectedProductVariant,\n getDisplayedProductVariantTypes,\n selectVariantValue,\n hasProductVariantStock,\n getProductVariantFormattedFinalPrice,\n \n // Cart functions\n addItemToCart,\n cartStore,\n \n // Navigation\n Router,\n \n // Customer\n customerStore,\n customerLogin,\n hasCustomer,\n \n // Favorites\n addIkasProductToFavorites,\n isFavoriteIkasProduct,\n\n // Type models\n IkasProduct,\n IkasProductVariant,\n IkasCart,\n IkasOrderLineItem,\n IkasCustomer,\n IkasOrder,\n IkasCategory,\n IkasBrand,\n IkasBlog\n} from \"@ikas/bp-storefront\";\n```\n\n### Store Instances\nPre-initialized MobX stores:\n```typescript\nimport { cartStore, customerStore } from \"@ikas/bp-storefront\";\n\n// Cart data\ncartStore.cart?.orderLineItems\n\n// Customer data \ncustomerStore.customer?.email\n```\n\n### Observer (for sub-components with reactive updates)\n```typescript\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span>Cart: {itemCount}</span>;\n});\n\n// Root export: plain function — ikas runtime handles reactivity via autorun()\nexport function MySection({ title }: Props) {\n return (\n <div>\n <h1>{title}</h1>\n <CartBadge />\n </div>\n );\n}\n\nexport default MySection;\n```\n\nDo **NOT** wrap root component exports with `observer()`. The ikas runtime handles root reactivity automatically via `autorun()`. Only wrap **sub-components** with `observer()` when they independently read from stores (`cartStore`, `customerStore`).\n\n### ⛔ Never deep-import from the SDK\n\nImport ONLY from the bare package root `@ikas/bp-storefront` (and likewise `@ikas/bp-storefront-models`/`-config`). **NEVER** import a subpath such as `@ikas/bp-storefront/dist/...` or `@ikas/bp-storefront/src/...`. Those private paths bundle without error but have NO window global in the published storefront, so the component throws at runtime — and `ikas-component build`/`dev` now hard-rejects them. If a helper looks missing from the root, it almost always exists there under a public name. Examples:\n\n- Coupon code → `getCouponCodeForm(customerStore)`, `initCouponCodeForm`, `setCouponCodeFormCouponCode`, `submitCouponCodeForm`, `removeCouponCodeForm`\n- Cart note / generic cart save → `saveCart`\n- Cart data → `cartStore`, `getCart`",
146
146
  "tags": [
147
147
  "import",
148
148
  "storefront",
@@ -173,7 +173,7 @@
173
173
  "common-pitfalls": {
174
174
  "title": "Common Pitfalls",
175
175
  "description": "Frequent mistakes LLMs and developers make when building ikas code components",
176
- "content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Root Component Should NOT Use observer\n\nThe ikas runtime wraps root component renders in `autorun()`, making them automatically reactive. Wrapping a root export with `observer()` is redundant and misleading.\n\n**Wrong** — observer on root export:\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nconst CartSection = observer(function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n});\nexport default CartSection;\n```\n\n**Correct** — plain named export:\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nexport function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n}\n\nexport default CartSection;\n```\n\n### 2. Observer Sub-Component Naming\n\nWhen using `observer()` on **sub-components**, use a named function expression — not an arrow function — for proper DevTools display names.\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst CartBadge = observer(() => {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n**Correct** — named function expression:\n```tsx\nconst CartBadge = observer(function CartBadge() {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n### 3. Mutation Semantics\n\nMany storefront functions (122+) return `void` and **mutate their arguments in place**. Do NOT try to capture a return value:\n\n```tsx\n// WRONG — selectVariantValue returns void, not a new product\nconst updated = selectVariantValue(product, value);\n\n// CORRECT — mutates product in place, observer re-renders automatically\nselectVariantValue(product, dvv.variantValue);\n```\n\nOther mutation functions: `initLoginForm()`, `setLoginFormEmail()`, `clearFilter()`, `selectFilterValue()`.\n\n### 4. CSS Scoping Limits\n\nOnly **class selectors** in `styles.css` are reliably scoped. Element selectors are NOT scoped:\n\n```css\n/* SAFE — scoped to your component */\n.product-card { padding: 16px; }\n.product-card .title { font-size: 18px; }\n\n/* UNSAFE — NOT reliably scoped, may affect other components */\ndiv { margin: 0; }\nh1 { font-size: 24px; }\n```\n\nAlways use class selectors for all styles.\n\n### 5. Prop Null Handling\n\nProps from the editor can be `undefined` when the store owner hasn't set them. Always use optional chaining:\n\n```tsx\n// WRONG — will crash if product is undefined\n<h1>{props.product.name}</h1>\n\n// CORRECT\n<h1>{props.product?.name}</h1>\n{props.heroImage && <img src={getDefaultSrc(props.heroImage)} />}\n```\n\n### 6. IkasProductImage vs IkasImage\n\n`variant.images` is `IkasProductImage[]`, NOT `IkasImage[]`. You must access the `.image` property to get the `IkasImage` needed by CDN helpers:\n\n```tsx\n// WRONG — productImage is IkasProductImage, not IkasImage\ngetDefaultSrc(productImage);\n\n// CORRECT — access .image to get IkasImage\ngetDefaultSrc(productImage.image);\n\n// Full pattern:\nconst images: IkasImage[] = variant.images\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n```\n\n### 7. Type Assertion Pattern\n\nSome storefront functions have type inference gaps. Use `as unknown as` casts when needed — this is a known pattern:\n\n```tsx\nconst inStock = hasProductStock(product) as unknown as boolean;\nconst finalPrice = getProductVariantFormattedFinalPrice(variant) as unknown as string;\nconst canAddToCart = isAddToCartEnabled(product) as unknown as boolean;\n```\n\nThis applies to functions like `hasProductStock`, `hasProductVariantStock`, `isAddToCartEnabled`, `hasProductVariantDiscount`, `getProductVariantDiscountPercentage`, `getProductVariantFormattedFinalPrice`, `getProductVariantFormattedSellPrice`, `getProductVariantFormattedDiscountAmount`, and `getProductVariantFormattedCampaignPrice`.\n\nNote: `getProductVariantMainImage` returns `IkasProductImage | undefined` (NOT `IkasImage`). Access `.image` to get the `IkasImage` for CDN helpers like `getDefaultSrc()`.\n\n### 8. Store Data Null Safety\n\nStore data (`customerStore.customer`, `cartStore.cart`, `baseStore`) is `null` before initialization completes. Always guard access:\n\n```tsx\n// WRONG — crashes if customer is null\n<h1>{customerStore.customer.firstName}</h1>\n\n// CORRECT — null check first\nif (!customerStore.customer) return <div>Loading...</div>;\n<h1>{customerStore.customer.firstName}</h1>\n\n// Also correct — optional chaining\n<h1>{customerStore.customer?.firstName ?? \"Guest\"}</h1>\n```\n\nSame for `cartStore.cart` — always use `cartStore.cart?.orderLineItems ?? []`.\n\n### 9. ProductList/BlogList Data Access\n\n`productList.data` is the correct way to access products in a product list. Similarly, `blogList.data` for blogs. The display names `.products` / `.blogs` are only used by the blueprint editor — they do NOT exist at runtime:\n\n```tsx\n// CORRECT — use .data for both product lists and blog lists\nconst products = productList?.data ?? [];\nconst blogs = blogList?.data ?? [];\n\n// WRONG — .products / .blogs are display names, not actual fields\nconst products = productList?.products ?? [];\nconst blogs = blogList?.blogs ?? [];\n```\n\n### 10. Form Field Access Pattern\n\nForm fields are objects with `.value`, `.label`, `.hasError`, `.message`. Never access the field directly as a primitive:\n\n```tsx\n// WRONG — loginForm.email is a field object, not a string\n<input value={loginForm.email} />\n\n// CORRECT — access .value for the actual value\n<input value={loginForm.email.value} />\n{loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n```\n\n### 11. Optional Chaining for Editor Props\n\nAll props from `ikas.config.json` can be `undefined` in the editor before the store owner sets them. Always use optional chaining and defaults:\n\n```tsx\n// WRONG — will crash in the editor\n<h1>{props.title}</h1>\n<img src={getDefaultSrc(props.image)} />\n{props.links.links.map(...)}\n\n// CORRECT — safe access with defaults\n<h1>{props.title ?? \"Default Title\"}</h1>\n{props.image && <img src={getDefaultSrc(props.image)} />}\n{(props.links?.links ?? []).map(...)}\n```\n\n### 12. Event Handler Typing\n\nPreact uses different event types than React. Use `(e: Event)` not `(e: React.ChangeEvent)`. Access values with casting. Preact uses `onInput` not `onChange` for text inputs:\n\n```tsx\n// WRONG — React patterns don't work in Preact\nonChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n\n// CORRECT — Preact event handling\nonInput={(e: Event) => setValue((e.target as HTMLInputElement).value)}\n```\n\nFor select elements:\n```tsx\nonChange={(e: Event) => setOption((e.target as HTMLSelectElement).value)}\n```\n\n### 13. Function Parameter Order\n\nMany storefront functions take specific parameter orders. Always verify with `get_function_doc()` before using:\n\n```tsx\n// WRONG — submitLoginForm only takes the form, not the store\nsubmitLoginForm(customerStore, loginForm);\n\n// CORRECT\nsubmitLoginForm(loginForm);\n\n// WRONG — wrong parameter order for addItemToCart\naddItemToCart(product, variant, 1);\n\n// CORRECT — variant first, then product, then quantity\naddItemToCart(variant, product, 1);\n\n// WRONG — selectVariantValue takes product and variantValue\nselectVariantValue(variant, value);\n\n// CORRECT\nselectVariantValue(product, dvv.variantValue);\n```\n\nWhen in doubt, use the `get_function_doc(functionName)` MCP tool to check the exact signature.\n\n### 14. Missing backgroundColor Prop on Sections\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background. Without it, the section has a hardcoded background that cannot be changed in the editor.\n\n**Wrong** — section with no backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" }\n]}\n```\n```tsx\nexport function MySection({ title }: Props) {\n return <section className=\"my-section\"><h1>{title}</h1></section>;\n}\n```\n\n**Correct** — section with backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" },\n { \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n]}\n```\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n </section>\n );\n}\n```\n\nOptionally, also consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the store owner changes the background color.\n\n### 15. Forgetting to Group Props for Complex Sections\n\nSections with 5+ props should use prop groups to organize the editor sidebar. Without groups, store owners see a long flat list of unrelated props.\n\n**Better** — organize related props into groups:\n```json\n{\n \"propGroups\": [\n { \"id\": \"content\", \"name\": \"Content\" },\n { \"id\": \"appearance\", \"name\": \"Appearance\" }\n ],\n \"props\": [\n { \"name\": \"heading\", \"groupId\": \"content\", ... },\n { \"name\": \"bgColor\", \"groupId\": \"appearance\", ... }\n ]\n}\n```\n\nSee `get_framework_guide(\"prop-groups\")` for full details.\n\n### 16. Hardcoded Static Text in Components\n\nAll user-visible text MUST be TEXT props — never hardcode strings in JSX. Hardcoded text cannot be translated for multilingual storefronts.\n\n**Wrong** — hardcoded strings:\n```tsx\nexport function LoginSection({ backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>Sign In</h1>\n <button>{form.isSubmitting ? \"Signing in...\" : \"Sign In\"}</button>\n <p>Don't have an account? <a href=\"/register\">Create one</a></p>\n </section>\n );\n}\n```\n\n**Correct** — all text as TEXT props with defaultValues:\n```json\n// ikas.config.json\n{ \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submitButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submittingButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Signing in...\", \"groupId\": \"texts\" },\n { \"name\": \"noAccountText\", \"type\": \"TEXT\", \"defaultValue\": \"Don't have an account?\", \"groupId\": \"texts\" },\n { \"name\": \"createAccountLinkText\", \"type\": \"TEXT\", \"defaultValue\": \"Create one\", \"groupId\": \"texts\" }\n],\n\"propGroups\": [{ \"id\": \"texts\", \"name\": \"Texts\" }]}\n```\n```tsx\nexport function LoginSection({ title = \"Sign In\", submitButtonText = \"Sign In\",\n submittingButtonText = \"Signing in...\", noAccountText = \"Don't have an account?\",\n createAccountLinkText = \"Create one\", backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n <button>{form.isSubmitting ? submittingButtonText : submitButtonText}</button>\n <p>{noAccountText} <a href=\"/register\">{createAccountLinkText}</a></p>\n </section>\n );\n}\n```\n\nFor button loading states, use two separate TEXT props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when the component has 5+ props.\n\n### 17. IkasOrderLineItemOption.values is an Array, Not a Scalar\n\n`IkasOrderLineItemOption` (the per-item options on cart/order line items) has a `.values` field that is an **array** of `IkasOrderLineItemOptionValue`, NOT a scalar `.value`. Each value in the array has its own `.name` / `.value` / `.formattedPrice`.\n\n```tsx\n// WRONG — there is no .value on IkasOrderLineItemOption\n<span>{option.value}</span>\n\n// CORRECT — .values is an array; map over it\n{option.values.map((v) => (\n <span key={v.name}>{v.name}: {v.value}</span>\n))}\n```\n\nThis is the most common confusion when rendering order summary lines or cart line item options. See `get_migration_guide(\"prop-runtime-shapes\")` for the broader catalog of order-shape gotchas.\n\n### 18. IkasOrderAdjustment Has No .formattedAmount — Use the Helper\n\n`IkasOrderAdjustment` does not expose a `.formattedAmount` property directly. Call the helper `getOrderAdjustmentFormattedAmount(adjustment)` from `@ikas/bp-storefront` — it returns the formatted currency string with the correct sign (negated for decrements).\n\n```tsx\nimport { getOrderAdjustmentFormattedAmount } from \"@ikas/bp-storefront\";\n\n// WRONG — .formattedAmount does not exist\n<td>{adjustment.formattedAmount}</td>\n\n// CORRECT\n<td>{getOrderAdjustmentFormattedAmount(adjustment)}</td>\n```\n\nSee `get_function_doc(\"getOrderAdjustmentFormattedAmount\")` for the exact signature.\n\n### 19. Old-System Property Patterns That Don't Exist in the New System\n\nSeveral `@ikas/storefront` properties look like they should still work but don't exist on the corresponding `@ikas/bp-storefront` types. Use the helper function instead. **If TypeScript reports TS2339 (\"Property X does not exist on type Y\") on a `.selectedVariant`, `.mainImage`, `.href`, or `.checkoutUrl` access, you're hitting one of these.**\n\n| Old pattern | New replacement |\n|---|---|\n| `product.selectedVariant` | `getSelectedProductVariant(product)` → `IkasProductVariant \\| undefined` |\n| `product.href` | `getProductHref(product)` (one of several `get*Href` helpers — see `navigation-patterns`) |\n| `variant.mainImage` | `getProductVariantMainImage(variant)` → `IkasProductImage`, then access `.image` for the `IkasImage` |\n| `cartStore.checkoutUrl` | `getCheckoutUrlFromCartStore(cartStore)` (handles auth-state branching internally) |\n\nThe variant-to-image chain is 4 hops (each carries metadata the next doesn't). See `get_framework_guide(\"image-handling\")` for the canonical recipe.",
176
+ "content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Root Component Should NOT Use observer\n\nThe ikas runtime wraps root component renders in `autorun()`, making them automatically reactive. Wrapping a root export with `observer()` is redundant and misleading.\n\n**Wrong** — observer on root export:\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nconst CartSection = observer(function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n});\nexport default CartSection;\n```\n\n**Correct** — plain named export:\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nexport function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n}\n\nexport default CartSection;\n```\n\n### 2. Observer Sub-Component Naming\n\nWhen using `observer()` on **sub-components**, use a named function expression — not an arrow function — for proper DevTools display names.\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst CartBadge = observer(() => {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n**Correct** — named function expression:\n```tsx\nconst CartBadge = observer(function CartBadge() {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n### 3. Mutation Semantics\n\nMany storefront functions (122+) return `void` and **mutate their arguments in place**. Do NOT try to capture a return value:\n\n```tsx\n// WRONG — selectVariantValue returns void, not a new product\nconst updated = selectVariantValue(product, value);\n\n// CORRECT — mutates product in place, observer re-renders automatically\nselectVariantValue(product, dvv.variantValue);\n```\n\nOther mutation functions: `initLoginForm()`, `setLoginFormEmail()`, `clearFilter()`, `selectFilterValue()`.\n\n### 4. CSS Scoping Limits\n\nOnly **class selectors** in `styles.css` are reliably scoped. Element selectors are NOT scoped:\n\n```css\n/* SAFE — scoped to your component */\n.product-card { padding: 16px; }\n.product-card .title { font-size: 18px; }\n\n/* UNSAFE — NOT reliably scoped, may affect other components */\ndiv { margin: 0; }\nh1 { font-size: 24px; }\n```\n\nAlways use class selectors for all styles.\n\n### 5. Prop Null Handling\n\nProps from the editor can be `undefined` when the store owner hasn't set them. Always use optional chaining:\n\n```tsx\n// WRONG — will crash if product is undefined\n<h1>{props.product.name}</h1>\n\n// CORRECT\n<h1>{props.product?.name}</h1>\n{props.heroImage && <img src={getDefaultSrc(props.heroImage)} />}\n```\n\n### 6. IkasProductImage vs IkasImage\n\n`variant.images` is `IkasProductImage[]`, NOT `IkasImage[]`. You must access the `.image` property to get the `IkasImage` needed by CDN helpers:\n\n```tsx\n// WRONG — productImage is IkasProductImage, not IkasImage\ngetDefaultSrc(productImage);\n\n// CORRECT — access .image to get IkasImage\ngetDefaultSrc(productImage.image);\n\n// Full pattern:\nconst images: IkasImage[] = variant.images\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n```\n\n### 7. Type Assertion Pattern\n\nSome storefront functions have type inference gaps. Use `as unknown as` casts when needed — this is a known pattern:\n\n```tsx\nconst inStock = hasProductStock(product) as unknown as boolean;\nconst finalPrice = getProductVariantFormattedFinalPrice(variant) as unknown as string;\nconst canAddToCart = isAddToCartEnabled(product) as unknown as boolean;\n```\n\nThis applies to functions like `hasProductStock`, `hasProductVariantStock`, `isAddToCartEnabled`, `hasProductVariantDiscount`, `getProductVariantDiscountPercentage`, `getProductVariantFormattedFinalPrice`, `getProductVariantFormattedSellPrice`, `getProductVariantFormattedDiscountAmount`, and `getProductVariantFormattedCampaignPrice`.\n\nNote: `getProductVariantMainImage` returns `IkasProductImage | undefined` (NOT `IkasImage`). Access `.image` to get the `IkasImage` for CDN helpers like `getDefaultSrc()`.\n\n### 8. Store Data Null Safety\n\nStore data (`customerStore.customer`, `cartStore.cart`, `baseStore`) is `null` before initialization completes. Always guard access:\n\n```tsx\n// WRONG — crashes if customer is null\n<h1>{customerStore.customer.firstName}</h1>\n\n// CORRECT — null check first\nif (!customerStore.customer) return <div>Loading...</div>;\n<h1>{customerStore.customer.firstName}</h1>\n\n// Also correct — optional chaining\n<h1>{customerStore.customer?.firstName ?? \"Guest\"}</h1>\n```\n\nSame for `cartStore.cart` — always use `cartStore.cart?.orderLineItems ?? []`.\n\n### 9. ProductList/BlogList Data Access\n\n`productList.data` is the correct way to access products in a product list. Similarly, `blogList.data` for blogs. The display names `.products` / `.blogs` are only used by the blueprint editor — they do NOT exist at runtime:\n\n```tsx\n// CORRECT — use .data for both product lists and blog lists\nconst products = productList?.data ?? [];\nconst blogs = blogList?.data ?? [];\n\n// WRONG — .products / .blogs are display names, not actual fields\nconst products = productList?.products ?? [];\nconst blogs = blogList?.blogs ?? [];\n```\n\n### 10. Form Field Access Pattern\n\nForm fields are objects with `.value`, `.label`, `.hasError`, `.message`. Never access the field directly as a primitive:\n\n```tsx\n// WRONG — loginForm.email is a field object, not a string\n<input value={loginForm.email} />\n\n// CORRECT — access .value for the actual value\n<input value={loginForm.email.value} />\n{loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n```\n\n### 11. Optional Chaining for Editor Props\n\nAll props from `ikas.config.json` can be `undefined` in the editor before the store owner sets them. Always use optional chaining and defaults:\n\n```tsx\n// WRONG — will crash in the editor\n<h1>{props.title}</h1>\n<img src={getDefaultSrc(props.image)} />\n{props.links.links.map(...)}\n\n// CORRECT — safe access with defaults\n<h1>{props.title ?? \"Default Title\"}</h1>\n{props.image && <img src={getDefaultSrc(props.image)} />}\n{(props.links?.links ?? []).map(...)}\n```\n\n### 12. Event Handler Typing\n\nPreact uses different event types than React. Use `(e: Event)` not `(e: React.ChangeEvent)`. Access values with casting. Preact uses `onInput` not `onChange` for text inputs:\n\n```tsx\n// WRONG — React patterns don't work in Preact\nonChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n\n// CORRECT — Preact event handling\nonInput={(e: Event) => setValue((e.target as HTMLInputElement).value)}\n```\n\nFor select elements:\n```tsx\nonChange={(e: Event) => setOption((e.target as HTMLSelectElement).value)}\n```\n\n### 13. Function Parameter Order\n\nMany storefront functions take specific parameter orders. Always verify with `get_function_doc()` before using:\n\n```tsx\n// WRONG — submitLoginForm only takes the form, not the store\nsubmitLoginForm(customerStore, loginForm);\n\n// CORRECT\nsubmitLoginForm(loginForm);\n\n// WRONG — wrong parameter order for addItemToCart\naddItemToCart(product, variant, 1);\n\n// CORRECT — variant first, then product, then quantity\naddItemToCart(variant, product, 1);\n\n// WRONG — selectVariantValue takes product and variantValue\nselectVariantValue(variant, value);\n\n// CORRECT\nselectVariantValue(product, dvv.variantValue);\n```\n\nWhen in doubt, use the `get_function_doc(functionName)` MCP tool to check the exact signature.\n\n### 14. Missing backgroundColor Prop on Sections\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background. Without it, the section has a hardcoded background that cannot be changed in the editor.\n\n**Wrong** — section with no backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" }\n]}\n```\n```tsx\nexport function MySection({ title }: Props) {\n return <section className=\"my-section\"><h1>{title}</h1></section>;\n}\n```\n\n**Correct** — section with backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" },\n { \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n]}\n```\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n </section>\n );\n}\n```\n\nOptionally, also consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the store owner changes the background color.\n\n### 15. Forgetting to Group Props for Complex Sections\n\nSections with 5+ props should use prop groups to organize the editor sidebar. Without groups, store owners see a long flat list of unrelated props.\n\n**Better** — organize related props into groups:\n```json\n{\n \"propGroups\": [\n { \"id\": \"content\", \"name\": \"Content\" },\n { \"id\": \"appearance\", \"name\": \"Appearance\" }\n ],\n \"props\": [\n { \"name\": \"heading\", \"groupId\": \"content\", ... },\n { \"name\": \"bgColor\", \"groupId\": \"appearance\", ... }\n ]\n}\n```\n\nSee `get_framework_guide(\"prop-groups\")` for full details.\n\n### 16. Hardcoded Static Text in Components\n\nAll user-visible text MUST be TEXT props — never hardcode strings in JSX. Hardcoded text cannot be translated for multilingual storefronts.\n\n**Wrong** — hardcoded strings:\n```tsx\nexport function LoginSection({ backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>Sign In</h1>\n <button>{form.isSubmitting ? \"Signing in...\" : \"Sign In\"}</button>\n <p>Don't have an account? <a href=\"/register\">Create one</a></p>\n </section>\n );\n}\n```\n\n**Correct** — all text as TEXT props with defaultValues:\n```json\n// ikas.config.json\n{ \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submitButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submittingButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Signing in...\", \"groupId\": \"texts\" },\n { \"name\": \"noAccountText\", \"type\": \"TEXT\", \"defaultValue\": \"Don't have an account?\", \"groupId\": \"texts\" },\n { \"name\": \"createAccountLinkText\", \"type\": \"TEXT\", \"defaultValue\": \"Create one\", \"groupId\": \"texts\" }\n],\n\"propGroups\": [{ \"id\": \"texts\", \"name\": \"Texts\" }]}\n```\n```tsx\nexport function LoginSection({ title = \"Sign In\", submitButtonText = \"Sign In\",\n submittingButtonText = \"Signing in...\", noAccountText = \"Don't have an account?\",\n createAccountLinkText = \"Create one\", backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n <button>{form.isSubmitting ? submittingButtonText : submitButtonText}</button>\n <p>{noAccountText} <a href=\"/register\">{createAccountLinkText}</a></p>\n </section>\n );\n}\n```\n\nFor button loading states, use two separate TEXT props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when the component has 5+ props.\n\n### 17. IkasOrderLineItemOption.values is an Array, Not a Scalar\n\n`IkasOrderLineItemOption` (the per-item options on cart/order line items) has a `.values` field that is an **array** of `IkasOrderLineItemOptionValue`, NOT a scalar `.value`. Each value in the array has its own `.name` / `.value` / `.formattedPrice`.\n\n```tsx\n// WRONG — there is no .value on IkasOrderLineItemOption\n<span>{option.value}</span>\n\n// CORRECT — .values is an array; map over it\n{option.values.map((v) => (\n <span key={v.name}>{v.name}: {v.value}</span>\n))}\n```\n\nThis is the most common confusion when rendering order summary lines or cart line item options. See `get_migration_guide(\"prop-runtime-shapes\")` for the broader catalog of order-shape gotchas.\n\n### 18. IkasOrderAdjustment Has No .formattedAmount — Use the Helper\n\n`IkasOrderAdjustment` does not expose a `.formattedAmount` property directly. Call the helper `getOrderAdjustmentFormattedAmount(adjustment)` from `@ikas/bp-storefront` — it returns the formatted currency string with the correct sign (negated for decrements).\n\n```tsx\nimport { getOrderAdjustmentFormattedAmount } from \"@ikas/bp-storefront\";\n\n// WRONG — .formattedAmount does not exist\n<td>{adjustment.formattedAmount}</td>\n\n// CORRECT\n<td>{getOrderAdjustmentFormattedAmount(adjustment)}</td>\n```\n\nSee `get_function_doc(\"getOrderAdjustmentFormattedAmount\")` for the exact signature.\n\n### 19. Old-System Property Patterns That Don't Exist in the New System\n\nSeveral `@ikas/storefront` properties look like they should still work but don't exist on the corresponding `@ikas/bp-storefront` types. Use the helper function instead. **If TypeScript reports TS2339 (\"Property X does not exist on type Y\") on a `.selectedVariant`, `.mainImage`, `.href`, or `.checkoutUrl` access, you're hitting one of these.**\n\n| Old pattern | New replacement |\n|---|---|\n| `product.selectedVariant` | `getSelectedProductVariant(product)` → `IkasProductVariant \\| undefined` |\n| `product.href` | `getProductHref(product)` (one of several `get*Href` helpers — see `navigation-patterns`) |\n| `variant.mainImage` | `getProductVariantMainImage(variant)` → `IkasProductImage`, then access `.image` for the `IkasImage` |\n| `cartStore.checkoutUrl` | `getCheckoutUrlFromCartStore(cartStore)` (handles auth-state branching internally) |\n\nThe variant-to-image chain is 4 hops (each carries metadata the next doesn't). See `get_framework_guide(\"image-handling\")` for the canonical recipe.\n\n### Deep SDK imports (`@ikas/bp-storefront/dist/...`)\n\n**Wrong** — reaching into a private subpath when a root helper seems missing:\n```tsx\n// breaks at runtime: no window global for this subpath, and the build rejects it\nimport { apiSaveCart } from \"@ikas/bp-storefront/dist/functions/api/cart\";\n```\n\n**Correct** — use the public root export (coupon has dedicated form helpers):\n```tsx\nimport {\n customerStore,\n getCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n} from \"@ikas/bp-storefront\";\n```\n\nRule: every import from the storefront SDK must come from the BARE package name — never a `/dist/` or `/src/` subpath.",
177
177
  "tags": [
178
178
  "pitfalls",
179
179
  "gotchas",
@@ -223,7 +223,7 @@
223
223
  "form-handling": {
224
224
  "title": "Form Handling",
225
225
  "description": "How to use the form model pattern for login, registration, address, and other forms",
226
- "content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n const loginForm = customerStore.loginForm;\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code | | `setCouponCode()` | `applyCouponCode()` |\n\n### Key Rules\n\n1. **Always call init first** — `init*Form()` sets up default field values, labels, and placeholders\n2. **Setter functions auto-validate** — if `form.isSubmitted` is true, calling any setter re-validates the form automatically\n3. **Check field.hasError for display** — show error messages only when `field.hasError` is true\n4. **Root components are automatically reactive** — forms use MobX observables which are automatically tracked in root components via `autorun()`. Use `observer()` only if you extract form logic into a sub-component\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
226
+ "content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n const loginForm = customerStore.loginForm;\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code | `getCouponCodeForm(customerStore)` → `initCouponCodeForm(form)` | `setCouponCodeFormCouponCode(form, value)` | `submitCouponCodeForm(form)` (remove with `removeCouponCodeForm(form)`) |\n\n### Key Rules\n\n1. **Always call init first** — `init*Form()` sets up default field values, labels, and placeholders\n2. **Setter functions auto-validate** — if `form.isSubmitted` is true, calling any setter re-validates the form automatically\n3. **Check field.hasError for display** — show error messages only when `field.hasError` is true\n4. **Root components are automatically reactive** — forms use MobX observables which are automatically tracked in root components via `autorun()`. Use `observer()` only if you extract form logic into a sub-component\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
227
227
  "tags": [
228
228
  "form",
229
229
  "login",
@@ -283,7 +283,7 @@
283
283
  "cart-patterns": {
284
284
  "title": "Cart Management Patterns (Serel Reference)",
285
285
  "description": "Serel CartPage section with CartItem sub-component, OrderSummary/CouponCode/EmptyState local components, cartStore patterns, and checkout flow",
286
- "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\nHandles coupon code input and submission:\n\n```tsx\nimport { initCouponCodeForm, setCouponCode, submitCouponCodeForm } from \"@ikas/bp-storefront\";\n\nfunction CouponCode({ cart }) {\n useEffect(() => { initCouponCodeForm(cart); }, []);\n const [code, setCode] = useState(\"\");\n\n const handleApply = async () => {\n setCouponCode(cart, code);\n await submitCouponCodeForm(cart);\n };\n\n return (\n <div className=\"coupon\">\n <input value={code} onInput={(e) => setCode((e.target as HTMLInputElement).value)} />\n <button onClick={handleApply}>Apply</button>\n </div>\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### 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.",
286
+ "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### 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.",
287
287
  "tags": [
288
288
  "cart",
289
289
  "checkout",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-06-11T10:33:25.738Z",
2
+ "generatedAt": "2026-06-29T13:00:01.253Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "apiListProductBrand",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-06-11T10:33:25.792Z",
2
+ "generatedAt": "2026-06-29T13:00:01.278Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "IkasProductAttributeDetail",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikas/code-components-mcp",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "MCP server for ikas code components documentation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",