@ikas/code-components-mcp 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,7 +51,7 @@
51
51
  "component-structure": {
52
52
  "title": "Component Structure",
53
53
  "description": "How to write a Preact component for ikas",
54
- "content": "Each component consists of three files. There are two patterns: **components** (child elements) and **sections** (page-level containers).\n\n## Component Pattern (child elements like buttons, cards, badges)\n\n### index.tsx - Component Implementation\n```tsx\nimport { Props } from \"./types\";\n\nexport default function MyComponent({ title, showButton }: Props) {\n return (\n <div className=\"my-component\">\n <h1>{title}</h1>\n {showButton && <button>Click me</button>}\n </div>\n );\n}\n```\n\n### types.ts - Props Interface\n```typescript\nexport interface Props {\n title: string; // TEXT prop -> string\n showButton?: boolean; // optional BOOLEAN prop\n count: number; // NUMBER prop -> number\n}\n```\n\n### styles.css\n```css\n.my-component {\n padding: 16px;\n}\n\n.my-component h1 {\n font-size: 24px;\n}\n```\n\n## Section Pattern (page-level containers like headers, hero banners, footers)\n\nSections use a `<section>` root element, full-width styling, and a `Props` interface. In `ikas.config.json` they have `\"type\": \"section\"`.\n\n### index.tsx - Section Implementation\n```tsx\nimport { Props } from \"./types\";\n\nexport default function HeroBanner({ heading, subtitle, backgroundColor }: Props) {\n return (\n <section className=\"hero-banner\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"hero-banner-inner\">\n <h2>{heading}</h2>\n {subtitle && <p>{subtitle}</p>}\n </div>\n </section>\n );\n}\n```\n\n### types.ts - Section Props Interface\n```typescript\nexport interface Props {\n heading: string;\n subtitle?: string;\n backgroundColor?: string;\n}\n```\n\n### styles.css - Full-width section styles\n```css\n.hero-banner {\n width: 100%;\n padding: 64px 24px;\n}\n\n.hero-banner-inner {\n max-width: 1200px;\n margin: 0 auto;\n}\n```\n\n## Key Rules\n- Export the component as `default export`\n- Use Preact (not React) - but JSX syntax is the same\n- Import types from `./types` for props\n- Use `className` not `class` for CSS classes\n- Storefront functions and types come from `@ikas/bp-storefront`\n- CSS classes are automatically scoped to your component at build time. Use plain CSS class selectors - they won't conflict with other components or the page.",
54
+ "content": "Each component consists of three files. There are two patterns: **components** (child elements) and **sections** (page-level containers).\n\n## Component Pattern (child elements like buttons, cards, badges)\n\n### index.tsx - Component Implementation\n```tsx\nimport { Props } from \"./types\";\n\nexport default function MyComponent({ title, showButton }: Props) {\n return (\n <div className=\"my-component\">\n <h1>{title}</h1>\n {showButton && <button>Click me</button>}\n </div>\n );\n}\n```\n\n### types.ts - Props Interface\n```typescript\nexport interface Props {\n title: string; // TEXT prop -> string\n showButton?: boolean; // optional BOOLEAN prop\n count: number; // NUMBER prop -> number\n}\n```\n\n### styles.css\n```css\n.my-component {\n padding: 16px;\n}\n\n.my-component h1 {\n font-size: 24px;\n}\n```\n\n## Section Pattern (page-level containers like headers, hero banners, footers)\n\nSections use a `<section>` root element, full-width styling, and a `Props` interface. In `ikas.config.json` they have `\"type\": \"section\"`.\n\n### index.tsx - Section Implementation\n```tsx\nimport { Props } from \"./types\";\n\nexport default function HeroBanner({ heading, subtitle, backgroundColor }: Props) {\n return (\n <section className=\"hero-banner\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"hero-banner-inner\">\n <h2>{heading}</h2>\n {subtitle && <p>{subtitle}</p>}\n </div>\n </section>\n );\n}\n```\n\n### types.ts - Section Props Interface\n```typescript\nexport interface Props {\n heading: string;\n subtitle?: string;\n backgroundColor?: string;\n}\n```\n\n### styles.css - Full-width section styles\n```css\n.hero-banner {\n width: 100%;\n padding: 64px 24px;\n}\n\n.hero-banner-inner {\n max-width: 1200px;\n margin: 0 auto;\n}\n```\n\n## Key Rules\n- Export the component as `default export`\n- Use Preact (not React) - but JSX syntax is the same\n- Import types from `./types` for props\n- Use `className` not `class` for CSS classes\n- Storefront functions and types come from `@ikas/bp-storefront`\n- CSS classes are automatically scoped to your component at build time. Use plain CSS class selectors - they won't conflict with other components or the page.\n\n## Using observer for Reactive Data\n\nWhen your component reads from MobX stores (`cartStore`, `customerStore`, etc.), wrap it with `observer()` from `@ikas/component-utils` so it re-renders when store data changes.\n\n### When to use observer\n- Your component reads `cartStore.cart`, `customerStore.customer`, or any other MobX observable\n- You display data that can change at runtime (cart count, login state, etc.)\n\n### When observer is NOT needed\n- Components that only use props passed from parent — no store reads\n- Static components with no reactive data\n\n### Example: Section with observer\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nconst CartSummary = observer(function CartSummary({ title }: Props) {\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return (\n <section className=\"cart-summary\">\n <h2>{title}</h2>\n <p>{itemCount} items in cart</p>\n </section>\n );\n});\n\nexport default CartSummary;\n```\n\n**Important:** When using `observer`, define the component as a named function expression (not arrow function) and assign it to a `const`, then export that `const` as default. This ensures proper display names in React DevTools.",
55
55
  "tags": [
56
56
  "component",
57
57
  "preact",
@@ -59,7 +59,10 @@
59
59
  "types",
60
60
  "css",
61
61
  "structure",
62
- "implementation"
62
+ "implementation",
63
+ "observer",
64
+ "reactive",
65
+ "stores"
63
66
  ]
64
67
  },
65
68
  "build-system": {
@@ -129,6 +132,24 @@
129
132
  "child",
130
133
  "structure"
131
134
  ]
135
+ },
136
+ "common-pitfalls": {
137
+ "title": "Common Pitfalls",
138
+ "description": "Frequent mistakes LLMs and developers make when building ikas code components",
139
+ "content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Observer Naming\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst MyComponent = observer(() => {\n return <div>...</div>;\n});\nexport default MyComponent;\n```\n\n**Correct** — named function expression:\n```tsx\nconst MyComponent = observer(function MyComponent({ title }: Props) {\n return <div>{title}</div>;\n});\nexport default MyComponent;\n```\n\nAlways use `const X = observer(function X() {...}); export default X;`. This ensures proper display names in React DevTools and avoids MobX warnings.\n\n### 2. 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### 3. 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### 4. 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?.src && <img src={props.heroImage.src} />}\n```\n\n### 5. 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### 6. 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`, and `getProductVariantMainImage`.",
140
+ "tags": ["pitfalls", "gotchas", "mistakes", "observer", "mutation", "css", "types"]
141
+ },
142
+ "form-handling": {
143
+ "title": "Form Handling",
144
+ "description": "How to use the form model pattern for login, registration, address, and other forms",
145
+ "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 { observer } from \"@ikas/component-utils\";\nimport {\n customerStore,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst LoginForm = observer(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```\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. **Use observer** — forms use MobX observables, so wrap your component with `observer()` to react to field changes\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
146
+ "tags": ["form", "login", "register", "address", "validation", "input"]
147
+ },
148
+ "async-data-patterns": {
149
+ "title": "Async Data & Loading Patterns",
150
+ "description": "How to handle async operations, loading states, and store data in ikas components",
151
+ "content": "## Async Data & Loading Patterns\n\n### 1. Store State is Null Until Initialized\n\nStore data (`cartStore.cart`, `customerStore.customer`) is `null` until the store initializes. Use `observer()` and null-safe access:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\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 in `observer()` components. 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 handles re-rendering automatically\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\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.",
152
+ "tags": ["async", "loading", "error", "stores", "cart", "fetch"]
132
153
  }
133
154
  }
134
155
  }