@ikas/code-components-mcp 1.4.0-beta.203 → 1.4.0-beta.205

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.
@@ -4,7 +4,7 @@
4
4
  "project-structure": {
5
5
  "title": "Project Structure",
6
6
  "description": "How an ikas code components project is organized",
7
- "content": "An ikas code components project follows this structure:\n\n```\nmy-project/\n├── src/\n│ ├── global.css # Optional: project-wide unscoped CSS (variables, resets)\n│ └── components/\n│ ├── MyComponent/\n│ │ ├── index.tsx # Component implementation (Preact)\n│ │ ├── types.ts # TypeScript props interface\n│ │ └── styles.css # Scoped component styles\n│ └── index.ts # Component exports barrel\n├── ikas.config.json # Component definitions, props, metadata\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\nEach component lives in its own directory under `src/components/`. The component's entry point is `index.tsx`, which exports a default Preact function component. The `types.ts` file defines the props interface matching the props declared in `ikas.config.json`. The `styles.css` file contains CSS that is automatically scoped to the component.\n\n### Project with Sub-Components\n\nWhen components grow complex, extract sub-components into `src/sub-components/`. See `get_framework_guide(\"sub-component-patterns\")` for full details.\n\n```\nmy-project/\n├── src/\n│ ├── components/\n│ │ ├── ProductList/\n│ │ │ ├── index.tsx # Imports from ../../sub-components/...\n│ │ │ ├── types.ts # Auto-generated (CLI-managed)\n│ │ │ └── styles.css # component styles (sub-component styles.css collected automatically)\n│ │ └── index.ts\n│ └── sub-components/\n│ ├── ProductCard/\n│ │ ├── index.tsx # Sub-component with inline Props\n│ │ └── styles.css # Sub-component styles\n│ └── FilterSidebar/\n│ ├── index.tsx\n│ └── styles.css\n├── ikas.config.json\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\n**Key:** `src/components/` = registered components (in ikas.config.json). `src/sub-components/` = internal helpers (NOT in ikas.config.json).",
7
+ "content": "An ikas code components project follows this structure:\n\n```\nmy-project/\n├── src/\n│ ├── global.css # Optional: project-wide CSS, scoped to this project (variables, fonts, resets)\n│ └── components/\n│ ├── MyComponent/\n│ │ ├── index.tsx # Component implementation (Preact)\n│ │ ├── types.ts # TypeScript props interface\n│ │ └── styles.css # Scoped component styles\n│ └── index.ts # Component exports barrel\n├── ikas.config.json # Component definitions, props, metadata\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\nEach component lives in its own directory under `src/components/`. The component's entry point is `index.tsx`, which exports a default Preact function component. The `types.ts` file defines the props interface matching the props declared in `ikas.config.json`. The `styles.css` file contains CSS that is automatically scoped to the component.\n\n### Project with Sub-Components\n\nWhen components grow complex, extract sub-components into `src/sub-components/`. See `get_framework_guide(\"sub-component-patterns\")` for full details.\n\n```\nmy-project/\n├── src/\n│ ├── components/\n│ │ ├── ProductList/\n│ │ │ ├── index.tsx # Imports from ../../sub-components/...\n│ │ │ ├── types.ts # Auto-generated (CLI-managed)\n│ │ │ └── styles.css # component styles (sub-component styles.css collected automatically)\n│ │ └── index.ts\n│ └── sub-components/\n│ ├── ProductCard/\n│ │ ├── index.tsx # Sub-component with inline Props\n│ │ └── styles.css # Sub-component styles\n│ └── FilterSidebar/\n│ ├── index.tsx\n│ └── styles.css\n├── ikas.config.json\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\n**Key:** `src/components/` = registered components (in ikas.config.json). `src/sub-components/` = internal helpers (NOT in ikas.config.json).",
8
8
  "tags": [
9
9
  "structure",
10
10
  "project",
@@ -114,20 +114,13 @@
114
114
  "css-scoping": {
115
115
  "title": "CSS Scoping Rules",
116
116
  "description": "How CSS is scoped to prevent conflicts between components",
117
- "content": "CSS files referenced in `ikas.config.json` are automatically scoped at build time.\n\n### How it works:\n- Each component's CSS selectors are prefixed with a unique scope identifier\n- `.my-class` becomes `.[scope]-my-class` in the output\n- This prevents style conflicts between different components on the same page\n\n### Best practices:\n- Use class selectors (`.my-class`) rather than element selectors (`div`, `h1`)\n- Avoid using `!important` - scoping handles isolation\n- Don't use global selectors or `@global` - they won't be scoped\n- Keep CSS in the component's `styles.css` file referenced in config, or in a sub-component's sibling `styles.css` — both are collected and scoped automatically on `dev`/`publish`\n- Nested selectors work: `.parent .child` both get scoped\n\n### Example:\n```css\n/* What you write */\n.product-card {\n padding: 16px;\n border: 1px solid #eee;\n}\n\n.product-card .title {\n font-size: 18px;\n font-weight: bold;\n}\n\n.product-card .price {\n color: #e53e3e;\n}\n```\n\nThe build system transforms this so your `.product-card`, `.title`, and `.price` classes are unique to your component and won't clash with any other component or theme styles.\n\n### Global CSS Exception\n\nThe one intentional exception to scoping is **Global CSS** — project-wide styles defined in `src/global.css` that are loaded unscoped before any component CSS. Global CSS is ideal for CSS custom properties (variables), resets, and shared utility classes. The loading order is: **Global CSS → Shared chunk CSS → Component CSS**, so global styles have the lowest specificity and component styles always win.\n\nSee `get_framework_guide(\"global-css\")` for full details on global CSS setup and usage.",
118
- "tags": [
119
- "css",
120
- "scoping",
121
- "styles",
122
- "isolation",
123
- "classes",
124
- "global"
125
- ]
117
+ "content": "CSS files referenced in `ikas.config.json` are automatically scoped at build time.\n\n### How it works:\n- Each component's CSS selectors are prefixed with a unique scope identifier\n- `.my-class` becomes `.[scope]-my-class` in the output\n- This prevents style conflicts between different components on the same page\n\n### Best practices:\n- Use class selectors (`.my-class`) rather than element selectors (`div`, `h1`)\n- Avoid using `!important` - scoping handles isolation\n- Don't use global selectors or `@global` - they won't be scoped\n- Keep CSS in the component's `styles.css` file referenced in config, or in a sub-component's sibling `styles.css` — both are collected and scoped automatically on `dev`/`publish`\n- Nested selectors work: `.parent .child` both get scoped\n\n### Example:\n```css\n/* What you write */\n.product-card {\n padding: 16px;\n border: 1px solid #eee;\n}\n\n.product-card .title {\n font-size: 18px;\n font-weight: bold;\n}\n\n.product-card .price {\n color: #e53e3e;\n}\n```\n\nThe build system transforms this so your `.product-card`, `.title`, and `.price` classes are unique to your component and won't clash with any other component or theme styles.\n\n### Global CSS Exception\n\nThe one intentional exception to component scoping is **Global CSS** — project-wide styles defined in `src/global.css`. It gets no `.cc_{componentId}` prefix, but it is still isolated to your project: every rule is wrapped in `:where([data-cc-scope~=\"<projectId>\"])`, and `@font-face` / `@keyframes` / `@counter-style` names declared there are prefixed with `cc_<projectId>_` (references in component CSS are rewritten to match automatically). Global CSS is ideal for CSS custom properties (variables), fonts, resets, and shared utility classes. The loading order is: **Global CSS → Shared chunk CSS → Component CSS**, so global styles have the lowest specificity and component styles always win.\n\nSee `get_framework_guide(\"global-css\")` for full details on global CSS setup and usage.",
118
+ "tags": ["css", "scoping", "styles", "isolation", "classes", "global"]
126
119
  },
127
120
  "global-css": {
128
121
  "title": "Global CSS",
129
- "description": "Project-wide unscoped CSS for variables, resets, and shared utilities",
130
- "content": "## Global CSS\n\nGlobal CSS lets you define project-wide styles that are shared across all components — without scoping. This is ideal for:\n\n- **CSS custom properties (variables)** — colors, spacing, typography tokens\n- **CSS resets** — normalize/reset base styles\n- **Base typography** — font-family, line-height defaults\n- **Utility classes** — common layout helpers\n\n### Setup\n\n1. Create a `src/global.css` file in your project root:\n\n```css\n/* src/global.css */\n:root {\n --color-primary: #2563eb;\n --color-secondary: #7c3aed;\n --color-text: #1f2937;\n --color-text-muted: #6b7280;\n --color-border: #e5e7eb;\n --color-background: #ffffff;\n\n --spacing-xs: 4px;\n --spacing-sm: 8px;\n --spacing-md: 16px;\n --spacing-lg: 24px;\n --spacing-xl: 48px;\n\n --font-sans: system-ui, -apple-system, sans-serif;\n --font-mono: ui-monospace, monospace;\n\n --radius-sm: 4px;\n --radius-md: 8px;\n --radius-lg: 16px;\n}\n```\n\n2. Add the `globalStyles` field to `ikas.config.json`:\n\n```json\n{\n \"name\": \"my-project\",\n \"version\": \"1.0.0\",\n \"globalStyles\": \"./src/global.css\",\n \"components\": [...]\n}\n```\n\n### Using Global CSS Variables in Components\n\nOnce defined in `global.css`, CSS custom properties are available in any component's `styles.css`:\n\n```css\n/* src/components/ProductCard/styles.css */\n.product-card {\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--spacing-md);\n font-family: var(--font-sans);\n}\n\n.product-card .title {\n color: var(--color-text);\n}\n\n.product-card .price {\n color: var(--color-primary);\n}\n```\n\n### Loading Order\n\nCSS is loaded in this order, from lowest to highest specificity:\n\n1. **Global CSS** (`global.css`) — unscoped, loaded first\n2. **Shared chunk CSS** — scoped styles from shared internal components (their sibling `styles.css` files, collected automatically)\n3. **Component CSS** (`styles.css`) — scoped per-component styles\n\nBecause global CSS loads first and is unscoped, component-scoped styles will always override global styles when selectors conflict. This means you can safely define base defaults in `global.css` and override them in component `styles.css` files.\n\n### Key Differences from Component CSS\n\n| | Global CSS | Component CSS |\n|---|---|---|\n| **File** | `src/global.css` | `src/components/X/styles.css` |\n| **Scoping** | NOT scoped (no `.cc_` prefix) | Scoped with `.cc_{componentId}` prefix |\n| **Config** | `globalStyles` in ikas.config.json | `styles` on each component |\n| **Purpose** | CSS variables, resets, utilities | Component-specific styles |\n| **Load order** | First (lowest specificity) | Last (highest specificity) |\n\n### Best Practices\n\n- **Use CSS custom properties** for design tokens (colors, spacing, fonts) — they cascade naturally and components can reference them\n- **Keep global CSS minimal** — only put truly shared values here. Component-specific styles belong in the component's `styles.css`\n- **Avoid class selectors in global CSS** that target component internals — use custom properties instead for themeable values\n- **Don't duplicate** — if a style only applies to one component, put it in that component's `styles.css`\n\n### Project Structure with Global CSS\n\n```\nmy-project/\n├── src/\n│ ├── global.css # Project-wide unscoped CSS\n│ └── components/\n│ ├── ProductCard/\n│ │ ├── index.tsx\n│ │ ├── types.ts\n│ │ └── styles.css # Uses var(--color-primary) etc.\n│ └── index.ts\n├── ikas.config.json # Has globalStyles: \"./src/global.css\"\n├── package.json\n└── tsconfig.json\n```\n\nSee `get_framework_guide(\"css-scoping\")` for details on how component CSS scoping works.",
122
+ "description": "Project-wide CSS (scoped to your project) for variables, fonts, resets, and shared utilities",
123
+ "content": "## Global CSS\n\nGlobal CSS lets you define project-wide styles that are shared across all components of your project. It is isolated per project at build time (every rule is wrapped in `:where([data-cc-scope~=\"<projectId>\"])`), but not per component. This is ideal for:\n\n- **CSS custom properties (variables)** — colors, spacing, typography tokens\n- **CSS resets** — normalize/reset base styles\n- **Base typography** — font-family, line-height defaults\n- **Utility classes** — common layout helpers\n\n### Setup\n\n1. Create a `src/global.css` file in your project root:\n\n```css\n/* src/global.css */\n:root {\n --color-primary: #2563eb;\n --color-secondary: #7c3aed;\n --color-text: #1f2937;\n --color-text-muted: #6b7280;\n --color-border: #e5e7eb;\n --color-background: #ffffff;\n\n --spacing-xs: 4px;\n --spacing-sm: 8px;\n --spacing-md: 16px;\n --spacing-lg: 24px;\n --spacing-xl: 48px;\n\n --font-sans: system-ui, -apple-system, sans-serif;\n --font-mono: ui-monospace, monospace;\n\n --radius-sm: 4px;\n --radius-md: 8px;\n --radius-lg: 16px;\n}\n```\n\n2. Add the `globalStyles` field to `ikas.config.json`:\n\n```json\n{\n \"name\": \"my-project\",\n \"version\": \"1.0.0\",\n \"globalStyles\": \"./src/global.css\",\n \"components\": [...]\n}\n```\n\n### Using Global CSS Variables in Components\n\nOnce defined in `global.css`, CSS custom properties are available in any component's `styles.css`:\n\n```css\n/* src/components/ProductCard/styles.css */\n.product-card {\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--spacing-md);\n font-family: var(--font-sans);\n}\n\n.product-card .title {\n color: var(--color-text);\n}\n\n.product-card .price {\n color: var(--color-primary);\n}\n```\n\n### Fonts, Keyframes and Counter Styles\n\n`@font-face`, `@keyframes` and `@counter-style` names live in a document-global namespace, so the build (editor preview and publish alike) prefixes every name declared in `global.css` with `cc_<projectId>_` — `\"Tanker\"` becomes `\"cc_<projectId>_Tanker\"`. You keep writing the plain name; references are rewritten to match automatically in:\n\n- component and shared-chunk CSS: `font`, `font-family`, `animation`, `animation-name`, `list-style`, `list-style-type`, `counter()` / `counters()`\n- CSS custom properties, for **font names only**: `--font-display: \"Tanker\", serif` is rewritten, so `font-family: var(--font-display)` works\n\n```css\n/* src/global.css */\n@font-face {\n font-family: \"Tanker\";\n /* absolute URL — local font files are not shipped with the project */\n src: url(\"https://cdn.example.com/tanker.woff2\") format(\"woff2\");\n font-display: swap;\n}\n\n:root {\n --font-display: \"Tanker\", Georgia, serif;\n}\n```\n\n```css\n/* src/components/Hero/styles.css */\n.hero-headline {\n font-family: var(--font-display); /* or: font-family: \"Tanker\", Georgia, serif; */\n}\n```\n\nNot rewritten — avoid these for names declared in `global.css`:\n\n- inline JS styles (`style={{ fontFamily: \"Tanker\" }}`) — use a class instead\n- keyframe / counter-style names inside custom properties (`--anim: fade`) — reference them from `animation` / `list-style` declarations directly\n- top-level `body { }` / `html { }` rules — they are dropped; style the elements you render (`.hero`, `img`, `[data-x]`)\n\nFonts that come from an external stylesheet (`@import url(\"https://fonts.googleapis.com/...\")` as the first rule of `global.css`) or from the theme's typography tokens (see `get_framework_guide(\"theme-globals\")`) keep their original names and need no rewriting.\n\n### Loading Order\n\nCSS is loaded in this order, from lowest to highest specificity:\n\n1. **Global CSS** (`global.css`) — project-scoped, loaded first\n2. **Shared chunk CSS** — scoped styles from shared internal components (their sibling `styles.css` files, collected automatically)\n3. **Component CSS** (`styles.css`) — scoped per-component styles\n\nBecause global CSS loads first and has the lowest specificity, component-scoped styles will always override global styles when selectors conflict. This means you can safely define base defaults in `global.css` and override them in component `styles.css` files.\n\n### Key Differences from Component CSS\n\n| | Global CSS | Component CSS |\n|---|---|---|\n| **File** | `src/global.css` | `src/components/X/styles.css` |\n| **Scoping** | Scoped per project (`[data-cc-scope]`); no `.cc_{componentId}` prefix | Scoped with `.cc_{componentId}` prefix |\n| **Config** | `globalStyles` in ikas.config.json | `styles` on each component |\n| **Purpose** | CSS variables, resets, utilities | Component-specific styles |\n| **Load order** | First (lowest specificity) | Last (highest specificity) |\n\n### Best Practices\n\n- **Use CSS custom properties** for design tokens (colors, spacing, fonts) — they cascade naturally and components can reference them (font names declared with `@font-face` in `global.css` are rewritten inside custom properties too — see \"Fonts, Keyframes and Counter Styles\")\n- **Keep global CSS minimal** — only put truly shared values here. Component-specific styles belong in the component's `styles.css`\n- **Avoid class selectors in global CSS** that target component internals — use custom properties instead for themeable values\n- **Don't duplicate** — if a style only applies to one component, put it in that component's `styles.css`\n\n### Project Structure with Global CSS\n\n```\nmy-project/\n├── src/\n│ ├── global.css # Project-wide CSS (scoped to the project)\n│ └── components/\n│ ├── ProductCard/\n│ │ ├── index.tsx\n│ │ ├── types.ts\n│ │ └── styles.css # Uses var(--color-primary) etc.\n│ └── index.ts\n├── ikas.config.json # Has globalStyles: \"./src/global.css\"\n├── package.json\n└── tsconfig.json\n```\n\nSee `get_framework_guide(\"css-scoping\")` for details on how component CSS scoping works.",
131
124
  "tags": [
132
125
  "css",
133
126
  "global",
@@ -136,7 +129,11 @@
136
129
  "reset",
137
130
  "utilities",
138
131
  "global.css",
139
- "globalStyles"
132
+ "globalStyles",
133
+ "font-face",
134
+ "fonts",
135
+ "keyframes",
136
+ "counter-style"
140
137
  ]
141
138
  },
142
139
  "dev-server": {
@@ -161,14 +158,7 @@
161
158
  "title": "Import Patterns",
162
159
  "description": "How to import storefront functions and types",
163
160
  "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`\n\n### Known misspelled export — import it verbatim\n\n`getIkasProductAttrributeProductIds` is spelled with a double **r** (\"Attrribute\"). That IS the real export name: it ships in `@ikas/bp-storefront` and is kept as-is because renaming a published export would break every theme importing it. Use it exactly as written — the correctly spelled `getIkasProductAttributeProductIds` does not exist and will not compile. Do NOT report it as a typo or \"fix\" the spelling. (In the blueprint editor its display alias is `getProductIds`, which hides the misspelling.)",
164
- "tags": [
165
- "import",
166
- "storefront",
167
- "models",
168
- "types",
169
- "observer",
170
- "stores"
171
- ]
161
+ "tags": ["import", "storefront", "models", "types", "observer", "stores"]
172
162
  },
173
163
  "sections-vs-components": {
174
164
  "title": "Sections vs Components",
@@ -267,14 +257,7 @@
267
257
  "title": "Async Data & Loading Patterns",
268
258
  "description": "How to handle async operations, loading states, and store data in ikas components",
269
259
  "content": "## Async Data & Loading Patterns\n\n### 1. Store State is Null Until Initialized\n\n**Note:** Root component exports are automatically reactive via the ikas runtime's `autorun()` — no `observer()` needed. The examples below show `observer()` on **sub-components** that independently read store data.\n\nStore data (`cartStore.cart`, `customerStore.customer`) is `null` until the store initializes. Use null-safe access:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n // cart is null until initialized — use optional chaining\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span className=\"cart-badge\">{itemCount}</span>;\n});\n```\n\n### 2. Loading Pattern for Async Operations\n\nUse `useState` for local loading flags. Always use `try/finally` to clear the loading state:\n\n```tsx\nimport { useState } from \"preact/hooks\";\nimport { addItemToCart } from \"@ikas/bp-storefront\";\n\nconst [isLoading, setIsLoading] = useState(false);\n\nconst handleAddToCart = async () => {\n if (isLoading) return; // prevent double-click\n setIsLoading(true);\n try {\n await addItemToCart(variant, product, 1);\n } finally {\n setIsLoading(false);\n }\n};\n\n// In JSX:\n<button disabled={isLoading} onClick={handleAddToCart}>\n {isLoading ? \"Adding...\" : \"Add to Cart\"}\n</button>\n```\n\n### 3. Cart Operation Results\n\n`addItemToCart` returns a result object. Check for validation errors:\n\n```tsx\nconst result = await addItemToCart(variant, product, 1);\nif (result.success) {\n // Item added successfully\n} else if (result.validationError === \"INSUFFICIENT_STOCK\") {\n // Not enough stock\n} else if (result.validationError === \"INVALID_PRODUCT_OPTION_VALUES\") {\n // Variant options not fully selected\n}\n```\n\n### 4. Form Submission Results\n\nForm submit functions return `boolean` and set status flags on the form:\n\n```tsx\nconst success = await submitLoginForm(loginForm);\nif (success) {\n // loginForm.isSuccess is true\n Router.navigate(\"/account\");\n} else {\n // loginForm.isFailure is true\n // loginForm.responseMessage has the error message\n console.log(loginForm.responseMessage);\n}\n```\n\n### 5. Observer Auto Re-rendering\n\nStore updates automatically trigger re-renders. Root components get this via `autorun()`, sub-components via `observer()`. You do NOT need manual state management for store-derived values:\n\n```tsx\n// WRONG — unnecessary useState for store data\nconst [cartItems, setCartItems] = useState([]);\nuseEffect(() => { setCartItems(cartStore.cart?.orderLineItems ?? []); }, [cartStore.cart]);\n\n// CORRECT — observer sub-component for independent reactivity\nconst CartList = observer(function CartList() {\n const items = cartStore.cart?.orderLineItems ?? [];\n return (\n <div>\n {items.map((item) => (\n <div key={item.id}>{item.variant?.name}</div>\n ))}\n </div>\n );\n});\n\n// Root export uses CartList — no observer needed on root\nexport function CartPage({ title }: Props) {\n return (\n <section>\n <h1>{title}</h1>\n <CartList />\n </section>\n );\n}\n\nexport default CartPage;\n```\n\n### 6. Pagination / Data Fetching\n\nList stores (blog, brand, category) use async pagination functions:\n\n```tsx\nimport { hasBlogListNextPage, getBlogListNextPage } from \"@ikas/bp-storefront\";\n\n// Check if more pages exist\nif (hasBlogListNextPage(blogList)) {\n // Fetch next page — mutates blogList in place, observer re-renders\n await getBlogListNextPage(blogList);\n}\n```\n\nThese functions mutate the list model in place. If your component uses `observer()`, it will re-render automatically when new data arrives.\n\nTo batch-fetch full products by id — e.g. enriching cart lines with the `salesChannels` limits or per-variant `stock` they don't carry — use `bs_searchProductsById(baseStore, { productIds })`: one request for all ids. See the \"Enforcing Limits on Cart Lines\" section in the `cart-patterns` guide for the full pattern.",
270
- "tags": [
271
- "async",
272
- "loading",
273
- "error",
274
- "stores",
275
- "cart",
276
- "fetch"
277
- ]
260
+ "tags": ["async", "loading", "error", "stores", "cart", "fetch"]
278
261
  },
279
262
  "product-detail-patterns": {
280
263
  "title": "Product Detail Patterns (Serel Reference)",
@@ -424,14 +407,7 @@
424
407
  "title": "Customer Review Patterns",
425
408
  "description": "Review display, star ratings, review submission form, and login-required checks",
426
409
  "content": "## Customer Review Patterns\n\nProduct reviews allow customers to rate and comment on products. The pattern includes displaying existing reviews and a submission form.\n\n### Displaying Reviews\n\n```tsx\nimport {\n getProductCustomerReviews,\n getIkasCustomerReviewFormattedDate,\n} from \"@ikas/bp-storefront\";\n\n// In your component (product prop):\nconst reviews = getProductCustomerReviews(product) ?? [];\n\n{reviews.map((review) => (\n <div key={review.id} className=\"review-card\">\n <div className=\"review-stars\">\n {[1, 2, 3, 4, 5].map((star) => (\n <span key={star} className={star <= review.star ? \"filled\" : \"empty\"}>★</span>\n ))}\n </div>\n <h4>{review.title}</h4>\n <p>{review.comment}</p>\n <span className=\"review-date\">{getIkasCustomerReviewFormattedDate(review)}</span>\n <span className=\"review-author\">{review.customerName}</span>\n </div>\n))}\n```\n\n### Review Submission Form\n\n```tsx\nimport {\n getIkasProductCustomerReviewForm,\n setCustomerReviewFormTitle,\n setCustomerReviewFormStar,\n setCustomerReviewFormComment,\n submitCustomerReviewForm,\n isCustomerReviewLoginRequired,\n customerStore,\n hasCustomer,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst reviewForm = getIkasProductCustomerReviewForm(product);\nconst loginRequired = isCustomerReviewLoginRequired() as unknown as boolean;\nconst isLoggedIn = hasCustomer(customerStore) as unknown as boolean;\n\n// If login is required and user is not logged in\nif (loginRequired && !isLoggedIn) {\n return (\n <div>\n <p>Please log in to write a review.</p>\n <button onClick={() => Router.navigateToPage(\"LOGIN\")}>Log In</button>\n </div>\n );\n}\n\n// Star rating input\nconst [hoverStar, setHoverStar] = useState(0);\n\n<div className=\"star-input\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n className={star <= (hoverStar || reviewForm.star.value) ? \"filled\" : \"empty\"}\n onMouseEnter={() => setHoverStar(star)}\n onMouseLeave={() => setHoverStar(0)}\n onClick={() => setCustomerReviewFormStar(reviewForm, star)}\n >\n ★\n </button>\n ))}\n</div>\n\n// Title and comment inputs\n<input\n value={reviewForm.title.value}\n onInput={(e) => setCustomerReviewFormTitle(reviewForm, (e.target as HTMLInputElement).value)}\n/>\n<textarea\n value={reviewForm.comment.value}\n onInput={(e) => setCustomerReviewFormComment(reviewForm, (e.target as HTMLTextAreaElement).value)}\n/>\n\n// Submit\nconst handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitCustomerReviewForm(reviewForm);\n if (success) {\n // Review submitted successfully\n }\n};\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getProductCustomerReviews(product)` | Get reviews for a product |\n| `getIkasProductCustomerReviewForm(product)` | Get the review form model |\n| `setCustomerReviewFormTitle(form, value)` | Set review title |\n| `setCustomerReviewFormStar(form, value)` | Set star rating (1-5) |\n| `setCustomerReviewFormComment(form, value)` | Set review comment |\n| `submitCustomerReviewForm(form)` | Submit the review |\n| `isCustomerReviewLoginRequired()` | Check if login is needed to review |\n| `getIkasCustomerReviewFormattedDate(review)` | Format review date |",
427
- "tags": [
428
- "reviews",
429
- "ratings",
430
- "stars",
431
- "feedback",
432
- "customer",
433
- "form"
434
- ]
410
+ "tags": ["reviews", "ratings", "stars", "feedback", "customer", "form"]
435
411
  },
436
412
  "slider-overlay-patterns": {
437
413
  "title": "Slider & Overlay Patterns (Serel Reference)",
@@ -456,14 +432,7 @@
456
432
  "title": "Blog Patterns",
457
433
  "description": "Blog list display, blog post detail, pagination, date formatting, and category filtering",
458
434
  "content": "## Blog Patterns\n\nBlog sections display articles with pagination and optional category filtering.\n\n### Blog List Prop Setup\n\nIn `ikas.config.json`, use the `BLOG_LIST` prop type or a dedicated blog list config:\n\n```json\n{\n \"name\": \"blogList\",\n \"displayName\": \"Blog List\",\n \"type\": \"BLOG_LIST\"\n}\n```\n\n### Blog List Display\n\n```tsx\nimport {\n IkasBlogList,\n getIkasBlogFormattedDate,\n getIkasBlogHref,\n hasBlogListNextPage,\n getBlogListNextPage,\n hasBlogListPrevPage,\n getBlogListPrevPage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// In your component:\nconst blogs = blogList?.data ?? [];\n\n{blogs.map((blog) => (\n <a key={blog.id} href={getIkasBlogHref(blog)} className=\"blog-card\">\n {blog.image && (\n <img src={getDefaultSrc(blog.image)} alt={blog.title} />\n )}\n <div className=\"blog-card-content\">\n <span className=\"blog-date\">{getIkasBlogFormattedDate(blog)}</span>\n <h3>{blog.title}</h3>\n <p>{blog.summary}</p>\n </div>\n </a>\n))}\n```\n\n### Blog Pagination\n\n```tsx\nconst hasNext = hasBlogListNextPage(blogList);\nconst hasPrev = hasBlogListPrevPage(blogList);\n\n<div className=\"blog-pagination\">\n {hasPrev && (\n <button onClick={() => getBlogListPrevPage(blogList)}>Previous</button>\n )}\n {hasNext && (\n <button onClick={() => getBlogListNextPage(blogList)}>Next</button>\n )}\n</div>\n```\n\n### Blog Post Detail\n\nFor a blog post detail page, use the `BLOG` prop type:\n\n```json\n{\n \"name\": \"blogPost\",\n \"displayName\": \"Blog Post\",\n \"type\": \"BLOG\"\n}\n```\n\n```tsx\ninterface Props {\n blogPost: IkasBlog;\n}\n\nfunction BlogPostDetail({ blogPost }: Props) {\n if (!blogPost) return null;\n\n return (\n <article className=\"blog-post\">\n {blogPost.image && (\n <img src={getDefaultSrc(blogPost.image)} alt={blogPost.title} />\n )}\n <h1>{blogPost.title}</h1>\n <span className=\"date\">{getIkasBlogFormattedDate(blogPost)}</span>\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: blogPost.blogContent.content }}\n />\n </article>\n );\n}\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getIkasBlogFormattedDate(blog)` | Format blog post date |\n| `getIkasBlogHref(blog)` | Get URL for a blog post |\n| `hasBlogListNextPage(list)` | Check if next page exists |\n| `getBlogListNextPage(list)` | Fetch next page (mutates list) |\n| `hasBlogListPrevPage(list)` | Check if previous page exists |\n| `getBlogListPrevPage(list)` | Fetch previous page (mutates list) |",
459
- "tags": [
460
- "blog",
461
- "article",
462
- "posts",
463
- "pagination",
464
- "date",
465
- "content"
466
- ]
435
+ "tags": ["blog", "article", "posts", "pagination", "date", "content"]
467
436
  },
468
437
  "navigation-patterns": {
469
438
  "title": "Navigation & Routing Patterns",
@@ -676,7 +645,7 @@
676
645
  "theme-globals": {
677
646
  "title": "Theme Global Settings & Design Tokens",
678
647
  "description": "Read and create the theme's global variables and design tokens (colors, typography, breakpoints, keyframes, color schemes) from code components",
679
- "content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className }`; breakpoints → `{ id, name, width }`; keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable\n// and travels unchanged across stores, so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, only the globals it DECLARES as dependencies travel with it. Write components so their global usage stays portable:\n\n- **Expose theme global variables as props — don't hardcode keys.** A prop bound to a global variable (via its `privateVarMap`) is detected and ships with the asset. A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` call with a hardcoded key in your source is NOT detected, so that variable won't travel and resolves to nothing in the destination store. If you need a theme setting, add it as a prop and read the prop, not a hardcoded key.\n- **Iterate the runtime lists instead of hardcoding token ids.** `getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`, and `getThemeColorSchemes()` return the DESTINATION store's own tokens — iterate them so the component adapts to whatever theme it lands in. A specific color/keyframe id hardcoded from the list won't exist in another store. (Color-SCHEME SLOT ids are the exception: a scheme travels in full with its slot ids intact and is matched by id on install — never re-keyed — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.)\n- **Travels automatically:** color schemes (in full — the slot/palette pattern above keeps working after install), types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: read named settings through props and read colors/typography/etc. by iterating the runtime lists — both are portable; a hardcoded global id in source is not.",
648
+ "content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className }`; breakpoints → `{ id, name, width }`; keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable\n// and travels unchanged across stores, so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW|ENUM); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`; ENUM: `enum_type_id` (a project enum id from `list_theme_globals` → `enumTypes[]`) + `value` = one of that enum's option VALUES (not a label). An ENUM setting reads back as `{ type: \"ENUM\", value, enumTypeId, options }`, an ENUM prop of the same enum can be bound to it with `bind_global`, and `themeValue(\"_<id>\")` from `./global-types` narrows to the enum's union type.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, only the globals it DECLARES as dependencies travel with it. Write components so their global usage stays portable:\n\n- **Expose theme global variables as props — don't hardcode keys.** A prop bound to a global variable (via its `privateVarMap`) is detected and ships with the asset. A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` call with a hardcoded key in your source is NOT detected, so that variable won't travel and resolves to nothing in the destination store. If you need a theme setting, add it as a prop and read the prop, not a hardcoded key.\n- **Iterate the runtime lists instead of hardcoding token ids.** `getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`, and `getThemeColorSchemes()` return the DESTINATION store's own tokens — iterate them so the component adapts to whatever theme it lands in. A specific color/keyframe id hardcoded from the list won't exist in another store. (Color-SCHEME SLOT ids are the exception: a scheme travels in full with its slot ids intact and is matched by id on install — never re-keyed — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.)\n- **Travels automatically:** color schemes (in full — the slot/palette pattern above keeps working after install), types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: read named settings through props and read colors/typography/etc. by iterating the runtime lists — both are portable; a hardcoded global id in source is not.",
680
649
  "tags": [
681
650
  "theme",
682
651
  "global variables",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-09-30T08:58:16.520Z",
2
+ "generatedAt": "2026-10-01T15:49:24.471Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "apiListProductBrand",
@@ -43,6 +43,20 @@
43
43
  "IkasStorefrontWidgetSettings"
44
44
  ]
45
45
  },
46
+ {
47
+ "name": "apiGetLoyaltyProgram",
48
+ "signature": "async function apiGetLoyaltyProgram(): Promise<APIResponse<IkasLoyaltyProgram>>",
49
+ "description": "",
50
+ "params": [],
51
+ "returnType": "Promise<APIResponse<IkasLoyaltyProgram>>",
52
+ "categories": [],
53
+ "related": [],
54
+ "isAsync": true,
55
+ "isClass": false,
56
+ "parameterTypes": [
57
+ "IkasLoyaltyProgram"
58
+ ]
59
+ },
46
60
  {
47
61
  "name": "apiListEarningMethods",
48
62
  "signature": "async function apiListEarningMethods(): Promise<APIResponse<IkasLoyaltyProgramEarningMethod[]>>",
@@ -15590,7 +15604,7 @@
15590
15604
  {
15591
15605
  "name": "getThemeSetting",
15592
15606
  "signature": "function getThemeSetting(name: string): ThemeSetting | undefined",
15593
- "description": "A single global variable by its stable key (`variableName`, e.g. `_6Q0KV7VGGM`).\nDiscover keys via (each item carries a human `displayName`).\nThe returned `value` is the live blueprint value when available, else the default.\nIts shape is discriminated by `type` (see ); note an\nIMAGE setting's value is an image REFERENCE (no `.url`) — resolve with\n`getDefaultSrc`/`getSrc`.",
15607
+ "description": "A single global variable by its stable key (`variableName`, e.g. `_6Q0KV7VGGM`).\nDiscover keys via (each item carries a human `displayName`).\nThe returned `value` is the live blueprint value when available, else the default.\nIts shape is discriminated by `type` (see ); note an\nIMAGE setting's value is an image REFERENCE (no `.url`) — resolve with\n`getDefaultSrc`/`getSrc`. An ENUM setting's `value` is one of its enum's option VALUES\n(or null) and the setting also carries `enumTypeId` and `options` (`{ label: value }`);\nin a code component prefer the typed `themeValue(\"_key\")` from `./global-types`, which\nnarrows the value to the enum's union.",
15594
15608
  "params": [],
15595
15609
  "returnType": "ThemeSetting | undefined",
15596
15610
  "categories": [],
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-09-30T08:58:16.562Z",
2
+ "generatedAt": "2026-10-01T15:49:24.502Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "TextElementTagTypes",
@@ -5083,11 +5083,21 @@
5083
5083
  "kind": "type",
5084
5084
  "domain": "loyalty-program",
5085
5085
  "properties": [
5086
+ {
5087
+ "name": "availablePoint",
5088
+ "type": "number",
5089
+ "optional": false
5090
+ },
5086
5091
  {
5087
5092
  "name": "cartLoyaltyLines",
5088
5093
  "type": "IkasCartLoyaltyLine[] | null",
5089
5094
  "optional": false
5090
5095
  },
5096
+ {
5097
+ "name": "cumulativeEarnedPoints",
5098
+ "type": "number",
5099
+ "optional": false
5100
+ },
5091
5101
  {
5092
5102
  "name": "customerTier",
5093
5103
  "type": "IkasLoyaltyProgramTier | null",
@@ -5224,6 +5234,11 @@
5224
5234
  "name": "validityPeriod",
5225
5235
  "type": "LoyaltyProgramPeriod | null",
5226
5236
  "optional": false
5237
+ },
5238
+ {
5239
+ "name": "waitingPeriodDays",
5240
+ "type": "number | null",
5241
+ "optional": false
5227
5242
  }
5228
5243
  ],
5229
5244
  "filePath": "loyalty-program/earning-method/index.ts",
@@ -5357,9 +5372,17 @@
5357
5372
  "name": "validityPeriod",
5358
5373
  "type": "LoyaltyProgramPeriod | null",
5359
5374
  "optional": false
5375
+ },
5376
+ {
5377
+ "name": "waitingPeriodDays",
5378
+ "type": "number | null",
5379
+ "optional": false
5360
5380
  }
5361
5381
  ],
5362
- "filePath": "loyalty-program/index.ts"
5382
+ "filePath": "loyalty-program/index.ts",
5383
+ "relatedFunctions": [
5384
+ "apiGetLoyaltyProgram"
5385
+ ]
5363
5386
  },
5364
5387
  {
5365
5388
  "name": "IkasLoyaltyProgramPointHistory",
@@ -5371,6 +5394,11 @@
5371
5394
  "type": "number",
5372
5395
  "optional": false
5373
5396
  },
5397
+ {
5398
+ "name": "availableDate",
5399
+ "type": "any | null",
5400
+ "optional": false
5401
+ },
5374
5402
  {
5375
5403
  "name": "createdAt",
5376
5404
  "type": "any | null",
@@ -14602,7 +14630,8 @@
14602
14630
  "NUMBER",
14603
14631
  "BOOLEAN",
14604
14632
  "BORDER",
14605
- "SHADOW"
14633
+ "SHADOW",
14634
+ "ENUM"
14606
14635
  ],
14607
14636
  "filePath": "theme-globals.ts"
14608
14637
  },
@@ -14632,7 +14661,19 @@
14632
14661
  "name": "value",
14633
14662
  "type": "any",
14634
14663
  "optional": false,
14635
- "description": "Concrete value, discriminated by `type`:\n- TEXT/RICH_TEXT → string · NUMBER → number · BOOLEAN → boolean · COLOR → hex string.\n- IMAGE → an IkasImage REFERENCE, e.g. `{ id: \"theme-images/<uuid>\" }`. It has NO\n `.url` field — resolve to a URL with `getDefaultSrc(value)` / `getSrc(value, size)`\n from `@ikas/bp-storefront`, never read `value.url`.\n- BORDER/SHADOW → object, or `null` when unset."
14664
+ "description": "Concrete value, discriminated by `type`:\n- TEXT/RICH_TEXT → string · NUMBER → number · BOOLEAN → boolean · COLOR → hex string.\n- IMAGE → an IkasImage REFERENCE, e.g. `{ id: \"theme-images/<uuid>\" }`. It has NO\n `.url` field — resolve to a URL with `getDefaultSrc(value)` / `getSrc(value, size)`\n from `@ikas/bp-storefront`, never read `value.url`.\n- BORDER/SHADOW → object, or `null` when unset.\n- ENUM → the selected option VALUE string (one of `Object.values(options)`, never a label),\n or `null` when unset or when that option no longer exists on the enum."
14665
+ },
14666
+ {
14667
+ "name": "enumTypeId",
14668
+ "type": "string",
14669
+ "optional": true,
14670
+ "description": "ENUM only — id of the project enum type (`project.types[].id`) this setting selects from. It is\nthe same id an ENUM component prop carries as `enumTypeId`; matching ids are what make a prop\nbindable to the setting."
14671
+ },
14672
+ {
14673
+ "name": "options",
14674
+ "type": "Record<string, string>",
14675
+ "optional": true,
14676
+ "description": "ENUM only — the enum's options at serialize time, `{ [label]: value }`. `value` above is always\none of these VALUES or `null`. Empty when the enum type could not be found in the project."
14636
14677
  }
14637
14678
  ],
14638
14679
  "filePath": "theme-globals.ts"
@@ -15173,6 +15214,12 @@
15173
15214
  "type": "string",
15174
15215
  "optional": true
15175
15216
  },
15217
+ {
15218
+ "name": "typeId",
15219
+ "type": "string",
15220
+ "optional": true,
15221
+ "description": "Blueprint type id. For an ENUM global this is the enum type's id (`project.types[].id`)."
15222
+ },
15176
15223
  {
15177
15224
  "name": "defaultValue",
15178
15225
  "type": "any",
@@ -15186,6 +15233,29 @@
15186
15233
  ],
15187
15234
  "filePath": "theme-globals.ts"
15188
15235
  },
15236
+ {
15237
+ "name": "TypeLike",
15238
+ "kind": "type",
15239
+ "domain": "theme",
15240
+ "properties": [
15241
+ {
15242
+ "name": "id",
15243
+ "type": "string",
15244
+ "optional": false
15245
+ },
15246
+ {
15247
+ "name": "type",
15248
+ "type": "unknown",
15249
+ "optional": true
15250
+ },
15251
+ {
15252
+ "name": "enumOptions",
15253
+ "type": "Record<string, any> | null",
15254
+ "optional": true
15255
+ }
15256
+ ],
15257
+ "filePath": "theme-globals.ts"
15258
+ },
15189
15259
  {
15190
15260
  "name": "SerializableProject",
15191
15261
  "kind": "type",
@@ -15196,6 +15266,12 @@
15196
15266
  "type": "{ module?: { variables?: VariableLike[] } }",
15197
15267
  "optional": true
15198
15268
  },
15269
+ {
15270
+ "name": "types",
15271
+ "type": "TypeLike[]",
15272
+ "optional": true,
15273
+ "description": "Project custom types; only enum entries are read, to resolve ENUM settings' options."
15274
+ },
15199
15275
  {
15200
15276
  "name": "theme",
15201
15277
  "type": "{\n breakpoints?: BreakpointLike[];\n keyframes?: KeyframeLike[];\n pattern?: {\n values?: PatternValueLike[];\n elements?: PatternElementLike[];\n colorSchemes?: ColorSchemeLike[];\n colorSchemeValues?: ColorSchemeValueLike[];\n };\n }",