@ikas/code-components-mcp 1.4.0-beta.4 → 1.4.0-beta.5

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,92 +4,243 @@
4
4
  "migration-overview": {
5
5
  "title": "Old Theme → Code Component Migration Overview",
6
6
  "description": "End-to-end workflow for converting an old ikas storefront theme into a new code-component project",
7
- "content": "This guide covers converting themes built with the old ikas storefront system (React, theme.json, @ikas/storefront, Tailwind CSS) into the new code-component system (Preact, ikas.config.json, @ikas/bp-storefront, scoped CSS).\n\n## FIRST: Choose a Workflow\n\n### For themes with >5 sections: USE THE ITERATIVE WORKFLOW\n\nLarge themes cannot be migrated in a single LLM session. Use the four-phase resumable workflow:\n\n1. **Phase A (once):** Call `plan_migration(theme_json, old_source_dir)` — generates MIGRATION.md with extracted CSS vars, enums, detected shared sub-components, and an ordered section queue with canonical component IDs.\n2. **Phase B (once):** Execute the foundation checklist — create global.css, run enum CLI commands, build shared sub-components.\n3. **Phase C (loop):** For each section, call `get_section_migration_plan(theme_json, section_name)` → run CLI commands → write `index.tsx` and `styles.css` → mark `[x]` in MIGRATION.md.\n4. **Phase D (any session):** First read MIGRATION.md, find the first `[ ]` item, resume.\n\n**See `get_migration_guide(\"iterative-workflow\")` for the full protocol.**\n\n### For small themes (< 5 sections): ONE-PASS WORKFLOW\n\n1. Call `analyze_old_theme` to understand the structure\n2. Generate ikas.config.json and all component files in one pass\n3. Build and verify\n\n## CRITICAL: The Two Systems Are Completely Different\n\n**The old storefront system (`@ikas/storefront`) and the new code-component system (`@ikas/bp-storefront`) are entirely separate systems with different packages, different data types, different APIs, and different runtime behaviors.** Even when type names look the same (e.g., `IkasImage`, `IkasProduct`, `IkasNavigationLink`), they are **different types from different packages** with potentially different properties and methods.\n\n**NEVER assume** that because a type exists in the old system, it works the same way in the new system:\n- `IkasImage` from `@ikas/storefront` ≠ `IkasImage` from `@ikas/bp-storefront` — different properties, different helper functions\n- `IkasProduct` from `@ikas/storefront` ≠ `IkasProduct` from `@ikas/bp-storefront` — different data shape\n- `IkasNavigationLink` from `@ikas/storefront` ≠ `IkasNavigationLink` from `@ikas/bp-storefront` — different structure\n- `IkasSlider` from `@ikas/storefront` → **does not exist** in the new system (replaced by plain `number`)\n- The old `<Image>` component → **does not exist** in the new system (use `getDefaultSrc()` + native `<img>`)\n- The old `useStore()` hook → **does not exist** in the new system (import stores directly)\n- The old `observer()` from `mobx-react-lite` → **must not be used** on root components (use `observer` from `@ikas/component-utils` only on sub-components)\n\n**Similarly, the prop type systems are completely different:**\n- Old system prop types (in theme.json) are defined by `IkasThemeJsonComponentPropType` enum — a different set of strings with different semantics\n- New system prop types (in ikas.config.json) are defined by the code-component `PropType` enum\n- Prop types with the same name (e.g., `TEXT`, `IMAGE`, `BOOLEAN`) may seem identical but produce **different TypeScript types** and have **different editor UI behaviors**\n- The old system has types that don't exist in the new system (`SLIDER`, `CUSTOM`, `PRODUCT_DETAIL`)\n- The new system has types that don't exist in the old system (`NUMBER`, `NUMBER_RANGE`, `DATE`, `TYPE`, `FUNCTION`, `COMPONENT`)\n\n**Always use the MCP server's `get_prop_types`, `get_type_definition`, `get_function_doc`, and `get_model_guide` tools** to look up the correct new-system types, functions, and APIs. Never copy old-system type usage into new-system code.\n\n## Migration Workflow\n\n### Step 1: Analyze the Old Theme\nCall `analyze_old_theme` with the old theme.json content. This produces:\n- A list of all components with their prop types\n- A map of all customData definitions (DYNAMIC_LIST, OBJECT, STATIC_LIST, ENUM)\n- Migration recommendations per component (what becomes a section, what becomes a child component)\n- Statistics (total props, CUSTOM props, SLIDER props, estimated child components)\n\n### Step 2: Plan the New Project Structure\nBased on the analysis:\n1. Each old component typically maps to a **section** in the new system\n2. Components with CUSTOM (DYNAMIC_LIST) props need **child components** — each DYNAMIC_LIST becomes a COMPONENT_LIST prop on the parent section, with a child component representing one item\n3. Components marked as header/footer get `isHeader: true` / `isFooter: true`\n4. Simple CUSTOM (OBJECT) props with ≤3 fields can be flattened into direct props on the parent\n5. Create custom enums for any ENUM-like customData\n\n### Step 3: Generate the Complete ikas.config.json\nCreate the full config with:\n- All sections (from old components)\n- All child components (from CUSTOM DYNAMIC_LIST props)\n- All custom enums (from ENUM customData)\n- All prop groups (from old groups)\n- Correct prop type mappings (see `get_migration_guide(\"prop-type-mapping\")`)\n\n### Step 4: Generate global.css\nConvert old theme settings to CSS variables:\n```css\n:root {\n /* From settings.colors */\n --primary: #fed9d9;\n --button-bg-1: #000000;\n /* From settings.fontFamily */\n font-family: 'Quicksand', sans-serif;\n}\n```\n\n### Step 5: Generate Component Source Files\nFor each component, generate:\n- `index.tsx` — Preact component (see `get_migration_guide(\"react-to-preact\")`)\n- `styles.css` — Scoped CSS (convert Tailwind to plain CSS)\n\nKey conversion rules:\n- Replace `@ikas/storefront` imports with `@ikas/bp-storefront`\n- Replace `observer(Component)` exports with plain `export default function Component`\n- Replace Swiper/Headless UI/etc. with vanilla implementations (see `get_migration_guide(\"library-replacements\")`)\n- Replace Tailwind classes with scoped CSS\n- Replace `IkasSlider` (old SLIDER type) with `number`\n- Replace `IkasComponentRenderer` from old system with `IkasComponentRenderer` from `@ikas/bp-storefront`\n\n### Step 6: Generate Sub-Components\nShared UI pieces (buttons, modals, cards) go in `src/sub-components/`. These are NOT registered in ikas.config.json — they're internal helpers imported by registered components.\n\n### Step 7: Build and Verify\nRun `npx ikas-component build` to compile. Fix any TypeScript or build errors.\n\n## Key Differences Summary\n\n| Old System | New System |\n|-----------|------------|\n| React 18 | Preact 10 |\n| @ikas/storefront | @ikas/bp-storefront |\n| theme.json | ikas.config.json |\n| Tailwind CSS | Scoped plain CSS |\n| External libraries allowed | No external libraries |\n| CUSTOM + customData | COMPONENT_LIST + child components |\n| SLIDER prop (IkasSlider) | NUMBER prop (number) |\n| PRODUCT_DETAIL | PRODUCT |\n| observer() on all components | observer() only on sub-components |\n| CSS Modules / Tailwind | .cc_{id} scoped CSS |\n| Next.js pages | Sections composed in editor |",
8
- "tags": ["migration", "overview", "workflow", "convert", "old-theme", "theme-json"]
7
+ "content": "This guide covers converting themes built with the old ikas storefront system (React, theme.json, @ikas/storefront, Tailwind CSS) into the new code-component system (Preact, ikas.config.json, @ikas/bp-storefront, scoped CSS).\n\n## FIRST: Choose a Workflow\n\n### For themes with >5 sections: USE THE ITERATIVE WORKFLOW\n\nLarge themes cannot be migrated in a single LLM session. Use the four-phase resumable workflow:\n\n1. **Phase A (once):** Call `plan_migration(theme_json, old_source_dir)` — generates MIGRATION.md with extracted CSS vars, enums, detected shared sub-components, and an ordered section queue with canonical component IDs.\n2. **Phase B (once):** Execute the foundation checklist — create global.css, run enum CLI commands, build shared sub-components.\n3. **Phase C (loop):** For each section, call `get_section_migration_plan(theme_json, section_name)` → run CLI commands → write `index.tsx` and `styles.css` → mark `[x]` in MIGRATION.md.\n4. **Phase D (any session):** First read MIGRATION.md, find the first `[ ]` item, resume.\n\n**See `get_migration_guide(\"iterative-workflow\")` for the full protocol.**\n\n### For small themes (< 5 sections): ONE-PASS WORKFLOW\n\n1. Call `analyze_old_theme` to understand the structure\n2. Generate ikas.config.json and all component files in one pass\n3. Build and verify\n\n## MCP Responsibilities vs LLM Responsibilities\n\nA successful migration depends on understanding what the MCP can and cannot do for you:\n\n- **MCP writes:** The initial `MIGRATION.md` (once, when you call `plan_migration` with `project_root`). Never again.\n- **MCP provides:** Reference data via read-only tools — section templates, type docs, framework guides, per-section migration plans.\n- **LLM owns:** All ongoing edits to `MIGRATION.md` — checkboxes, custom-data decisions, source-code-analysis findings, notes. Use your standard file-editing tools.\n- **LLM must do:** Source code analysis. `theme.json` does NOT list atomic UI primitives (Button, Input, Card, icons, layout helpers). These live only in old `src/` and the MCP cannot see them. You scan; you log them in MIGRATION.md.\n- **LLM decides:** Every customData enum-vs-component choice. The MCP's auto-classification (flat scalars → enum, structured records → component) is a default — verify each one against the actual data semantics.\n\nThere is no `resume_migration`, `mark_section_complete`, or `record_custom_data_decision` tool. By design, the LLM uses its own Read/Edit tools to update MIGRATION.md. The MCP's job is to give you a high-quality starting scaffold and ongoing reference data.\n\n## CRITICAL: The Two Systems Are Completely Different\n\n**The old storefront system (`@ikas/storefront`) and the new code-component system (`@ikas/bp-storefront`) are entirely separate systems with different packages, different data types, different APIs, and different runtime behaviors.** Even when type names look the same (e.g., `IkasImage`, `IkasProduct`, `IkasNavigationLink`), they are **different types from different packages** with potentially different properties and methods.\n\n**NEVER assume** that because a type exists in the old system, it works the same way in the new system:\n- `IkasImage` from `@ikas/storefront` ≠ `IkasImage` from `@ikas/bp-storefront` — different properties, different helper functions\n- `IkasProduct` from `@ikas/storefront` ≠ `IkasProduct` from `@ikas/bp-storefront` — different data shape\n- `IkasNavigationLink` from `@ikas/storefront` ≠ `IkasNavigationLink` from `@ikas/bp-storefront` — different structure\n- `IkasSlider` from `@ikas/storefront` → **does not exist** in the new system (replaced by plain `number`)\n- The old `<Image>` component → **does not exist** in the new system (use `getDefaultSrc()` + native `<img>`)\n- The old `useStore()` hook → **does not exist** in the new system (import stores directly)\n- The old `observer()` from `mobx-react-lite` → **must not be used** on root components (use `observer` from `@ikas/component-utils` only on sub-components)\n\n**Similarly, the prop type systems are completely different:**\n- Old system prop types (in theme.json) are defined by `IkasThemeJsonComponentPropType` enum — a different set of strings with different semantics\n- New system prop types (in ikas.config.json) are defined by the code-component `PropType` enum\n- Prop types with the same name (e.g., `TEXT`, `IMAGE`, `BOOLEAN`) may seem identical but produce **different TypeScript types** and have **different editor UI behaviors**\n- The old system has types that don't exist in the new system (`SLIDER`, `CUSTOM`, `PRODUCT_DETAIL`)\n- The new system has types that don't exist in the old system (`NUMBER`, `NUMBER_RANGE`, `DATE`, `TYPE`, `FUNCTION`, `COMPONENT`)\n\n**Always use the MCP server's `get_prop_types`, `get_type_definition`, `get_function_doc`, and `get_model_guide` tools** to look up the correct new-system types, functions, and APIs. Never copy old-system type usage into new-system code.\n\n## Migration Workflow\n\n### Step 1: Analyze the Old Theme\nCall `analyze_old_theme` with the old theme.json content. This produces:\n- A list of all components with their prop types\n- A map of all customData definitions (DYNAMIC_LIST, OBJECT, STATIC_LIST, ENUM)\n- Migration recommendations per component (what becomes a section, what becomes a child component)\n- Statistics (total props, CUSTOM props, SLIDER props, estimated child components)\n\n### Step 2: Plan the New Project Structure\nBased on the analysis:\n1. Each old component typically maps to a **section** in the new system\n2. Components with CUSTOM (DYNAMIC_LIST) props need **child components** — each DYNAMIC_LIST becomes a COMPONENT_LIST prop on the parent section, with a child component representing one item\n3. Components marked as header/footer get `isHeader: true` / `isFooter: true`\n4. Simple CUSTOM (OBJECT) props with ≤3 fields can be flattened into direct props on the parent\n5. Create custom enums for any ENUM-like customData\n\n### Step 3: Generate the Complete ikas.config.json\nCreate the full config with:\n- All sections (from old components)\n- All child components (from CUSTOM DYNAMIC_LIST props)\n- All custom enums (from ENUM customData)\n- All prop groups (from old groups)\n- Correct prop type mappings (see `get_migration_guide(\"prop-type-mapping\")`)\n\n### Step 4: Generate global.css\nConvert old theme settings to CSS variables:\n```css\n:root {\n /* From settings.colors */\n --primary: #fed9d9;\n --button-bg-1: #000000;\n /* From settings.fontFamily */\n font-family: 'Quicksand', sans-serif;\n}\n```\n\n### Step 5: Generate Component Source Files\nFor each component, generate:\n- `index.tsx` — Preact component (see `get_migration_guide(\"react-to-preact\")`)\n- `styles.css` — Scoped CSS (convert Tailwind to plain CSS)\n\nKey conversion rules:\n- Replace `@ikas/storefront` imports with `@ikas/bp-storefront`\n- Replace `observer(Component)` exports with plain `export default function Component`\n- Replace Swiper/Headless UI/etc. with vanilla implementations (see `get_migration_guide(\"library-replacements\")`)\n- Replace Tailwind classes with scoped CSS\n- Replace `IkasSlider` (old SLIDER type) with `number`\n- Replace `IkasComponentRenderer` from old system with `IkasComponentRenderer` from `@ikas/bp-storefront`\n\n### Step 6: Generate Sub-Components\nShared UI pieces (buttons, modals, cards) go in `src/sub-components/`. These are NOT registered in ikas.config.json — they're internal helpers imported by registered components.\n\n### Step 7: Build and Verify\nRun `npx ikas-component build` to compile. Fix any TypeScript or build errors.\n\n## Key Differences Summary\n\n| Old System | New System |\n|-----------|------------|\n| React 18 | Preact 10 |\n| @ikas/storefront | @ikas/bp-storefront |\n| theme.json | ikas.config.json |\n| Tailwind CSS | Scoped plain CSS |\n| External libraries allowed | No external libraries |\n| CUSTOM + customData | COMPONENT_LIST + child components |\n| SLIDER prop (IkasSlider) | NUMBER prop (number) |\n| PRODUCT_DETAIL | PRODUCT |\n| observer() on all components | observer() only on sub-components |\n| CSS Modules / Tailwind | .cc_{id} scoped CSS |\n| Next.js pages | Sections composed in editor |",
8
+ "tags": [
9
+ "migration",
10
+ "overview",
11
+ "workflow",
12
+ "convert",
13
+ "old-theme",
14
+ "theme-json"
15
+ ]
9
16
  },
10
17
  "prop-type-mapping": {
11
18
  "title": "Prop Type Mapping: Old → New",
12
19
  "description": "Complete mapping of old theme.json prop types to new ikas.config.json prop types",
13
20
  "content": "## IMPORTANT: Different Systems, Different Types\n\n**The old prop type system (theme.json) and the new prop type system (ikas.config.json) are completely separate.** Even when a prop type has the same name in both systems (e.g., `IMAGE`, `LINK`, `PRODUCT_LIST`), the underlying TypeScript types come from different packages (`@ikas/storefront` vs `@ikas/bp-storefront`) and may have different properties, methods, and nullability. Always use the MCP tools (`get_type_definition`, `get_model_guide`) to look up the exact new-system types — never assume old-system knowledge applies.\n\n## Direct Mappings (1:1)\n\nThese prop types have similar equivalents, but remember the TypeScript types are from different packages:\n\n| Old Type | New Type | TypeScript (Old) | TypeScript (New) | Notes |\n|----------|----------|-----------------|-----------------|-------|\n| `TEXT` | `TEXT` | `string` | `string` | |\n| `RICH_TEXT` | `RICH_TEXT` | `string` | `string` | |\n| `BOOLEAN` | `BOOLEAN` | `boolean` | `boolean` | |\n| `IMAGE` | `IMAGE` | `IkasImage` | `IkasImage \\| null` | New type is nullable |\n| `IMAGE_LIST` | `IMAGE_LIST` | `IkasImage[]` | `IkasImageList` | Slight type difference |\n| `LINK` | `LINK` | `IkasNavigationLink` | `IkasNavigationLink` | |\n| `LIST_OF_LINK` | `LIST_OF_LINK` | `IkasNavigationLink[]` | `IkasNavigationLinkList` | Slight type difference |\n| `COLOR` | `COLOR` | `string` | `string` | |\n| `VIDEO` | `VIDEO` | `IkasVideo` | `IkasVideo \\| null` | New type is nullable |\n| `PRODUCT_LIST` | `PRODUCT_LIST` | `IkasProductList` | `IkasProductList` | |\n| `CATEGORY` | `CATEGORY` | `IkasCategory` | `IkasCategory \\| null` | |\n| `CATEGORY_LIST` | `CATEGORY_LIST` | `IkasCategoryList` | `IkasCategoryList` | |\n| `BRAND` | `BRAND` | `IkasBrand` | `IkasBrand \\| null` | |\n| `BRAND_LIST` | `BRAND_LIST` | — | `IkasBrandList` | |\n| `BLOG` | `BLOG` | `IkasBlog` | `IkasBlog \\| null` | |\n| `BLOG_LIST` | `BLOG_LIST` | `IkasBlogList` | `IkasBlogList` | |\n| `BLOG_CATEGORY` | `BLOG_CATEGORY` | `IkasBlogCategory` | `IkasBlogCategory \\| null` | |\n| `BLOG_CATEGORY_LIST` | `BLOG_CATEGORY_LIST` | `IkasBlogCategoryList` | `IkasBlogCategoryList` | |\n| `PRODUCT_ATTRIBUTE` | `PRODUCT_ATTRIBUTE` | `IkasAttributeDetail` | `IkasProductAttributeValue \\| null` | Type renamed |\n| `PRODUCT_ATTRIBUTE_LIST` | `PRODUCT_ATTRIBUTE_LIST` | `IkasAttributeList` | `IkasProductAttributeValue[]` | Type renamed |\n| `COMPONENT_LIST` | `COMPONENT_LIST` | `IkasComponentRenderer` | `any` (rendered via `<IkasComponentRenderer>`) | |\n\n## Renamed Types\n\n| Old Type | New Type | Notes |\n|----------|----------|-------|\n| `PRODUCT_DETAIL` | `PRODUCT` | Same concept, just renamed. Old: `IkasProduct`, New: `IkasProduct \\| null` |\n\n## Types Requiring Conversion\n\n### SLIDER → NUMBER\nOld `SLIDER` type used `IkasSlider` with `{ value: number }`. The new system uses `NUMBER` which maps to plain `number`.\n\n**Old theme.json:**\n```json\n{\n \"name\": \"logoWidth\",\n \"type\": \"SLIDER\",\n \"sliderData\": { \"min\": 80, \"max\": 300, \"interval\": 1 }\n}\n```\n\n**New ikas.config.json:**\n```json\n{\n \"name\": \"logoWidth\",\n \"displayName\": \"Logo Width\",\n \"type\": \"NUMBER\",\n \"required\": false,\n \"defaultValue\": 120\n}\n```\n\n**Code change:** Replace `logoWidth?.value` or `logoWidth.value` with just `logoWidth` (it's already a number).\n\n### CUSTOM → Multiple approaches\nSee `get_migration_guide(\"custom-data-conversion\")` for the full guide. Summary:\n\n| Old customData type | New approach |\n|--------------------|--------------|\n| DYNAMIC_LIST (list of objects) | `COMPONENT_LIST` + child component |\n| OBJECT (≤3 simple fields) | Flatten into direct props on parent |\n| OBJECT (>3 fields or complex) | `COMPONENT_LIST` + child component |\n| STATIC_LIST (fixed count items) | `COMPONENT_LIST` (store owner manages count) |\n| ENUM (enumOptions) | `ENUM` with custom enum via `config add-enum` |\n\n### ENUM → ENUM\nOld ENUM type with `enumOptions` maps to new ENUM with `enumTypeId`. Create custom enums first:\n```bash\nnpx ikas-component config add-enum --name \"AspectRatio\" --options '{\"Square\":\"1:1\",\"Landscape\":\"16:9\",\"Portrait\":\"3:4\"}'\n# Use returned enumId in prop definition\n```\n\n## Types Not Available in New System\n\nThese old types do not exist in the new system and need workarounds:\n\n| Old Type | Workaround |\n|----------|------------|\n| `SLIDER` | Use `NUMBER`. Access value directly instead of `.value` |\n| `OBJECT` (customData) | Flatten into parent props or use COMPONENT_LIST |\n| `STATIC_LIST` (customData) | Use COMPONENT_LIST |\n\n## New Types Not in Old System\n\n| New Type | Description |\n|----------|-------------|\n| `NUMBER` | Numeric input (replaces SLIDER) |\n| `NUMBER_RANGE` | Two-number range (IkasNumberRange) |\n| `DATE` | Date picker |\n| `FUNCTION` | Function prop (callback) |\n| `TYPE` | Structured style type (padding, margin, etc.) |\n| `COMPONENT` | Single child component slot |\n| `RAFFLE` / `RAFFLE_LIST` | Raffle references |",
14
- "tags": ["migration", "props", "types", "mapping", "SLIDER", "CUSTOM", "PRODUCT_DETAIL", "conversion"]
21
+ "tags": [
22
+ "migration",
23
+ "props",
24
+ "types",
25
+ "mapping",
26
+ "SLIDER",
27
+ "CUSTOM",
28
+ "PRODUCT_DETAIL",
29
+ "conversion"
30
+ ]
15
31
  },
16
32
  "custom-data-conversion": {
17
33
  "title": "Converting CUSTOM Props and customData",
18
34
  "description": "How to convert old CUSTOM prop types with customData definitions to new COMPONENT_LIST + child components",
19
- "content": "The old system's `CUSTOM` prop type + `customData` definitions are the most complex migration. Each approach depends on the customData structure.\n\n## Decision Tree\n\n1. **Is it a DYNAMIC_LIST?** → Use `COMPONENT_LIST` + child component\n2. **Is it an OBJECT with ≤3 simple fields?** → Flatten into direct props on parent\n3. **Is it an OBJECT with >3 fields or complex nested types?** → Use `COMPONENT_LIST` + child component\n4. **Is it a STATIC_LIST?** → Use `COMPONENT_LIST` (store owner manages item count)\n5. **Is it an ENUM?** → Use `ENUM` prop with custom enum\n6. **Does it have nested DYNAMIC_LIST inside?** → Use nested COMPONENT_LIST or flatten\n\n## Example 1: DYNAMIC_LIST → COMPONENT_LIST + Child Component\n\nThis is the most common conversion. A list of custom items becomes a section with a COMPONENT_LIST prop and a child component.\n\n### Old System\n\n**customData definition (in theme.json):**\n```json\n{\n \"id\": \"b361dc09-...\",\n \"name\": \"Mağazalarımız\",\n \"type\": \"DYNAMIC_LIST\",\n \"nestedData\": [{\n \"name\": \"Mağaza\",\n \"typescriptName\": \"Maaza\",\n \"type\": \"OBJECT\",\n \"nestedData\": [\n { \"name\": \"Başlık\", \"type\": \"TEXT\", \"key\": \"title\" },\n { \"name\": \"Lokasyon\", \"type\": \"TEXT\", \"key\": \"location\" },\n { \"name\": \"Görsel\", \"type\": \"IMAGE\", \"key\": \"image\" }\n ]\n }]\n}\n```\n\n**Component prop (in theme.json):**\n```json\n{ \"name\": \"stores\", \"type\": \"CUSTOM\", \"customDataId\": \"b361dc09-...\" }\n```\n\n**Old generated type:** `stores: Maaza[]` where `Maaza = { title: string; location: string; image?: IkasImage }`\n\n**Old React code:**\n```tsx\n{stores.map((store, i) => (\n <div key={i}>\n <Image image={store.image} layout=\"fill\" />\n <h3>{store.title}</h3>\n <p>{store.location}</p>\n </div>\n))}\n```\n\n### New System\n\n**ikas.config.json — Parent section:**\n```json\n{\n \"id\": \"my-project-stores-section\",\n \"name\": \"StoresSection\",\n \"type\": \"section\",\n \"entry\": \"./src/components/StoresSection/index.tsx\",\n \"styles\": \"./src/components/StoresSection/styles.css\",\n \"props\": [\n {\n \"name\": \"stores\",\n \"displayName\": \"Stores\",\n \"type\": \"COMPONENT_LIST\",\n \"required\": false,\n \"filteredComponentIds\": [\"my-project-store-card\"]\n }\n ]\n}\n```\n\n**ikas.config.json — Child component:**\n```json\n{\n \"id\": \"my-project-store-card\",\n \"name\": \"StoreCard\",\n \"type\": \"component\",\n \"entry\": \"./src/components/StoreCard/index.tsx\",\n \"styles\": \"./src/components/StoreCard/styles.css\",\n \"props\": [\n { \"name\": \"title\", \"displayName\": \"Title\", \"type\": \"TEXT\", \"required\": true },\n { \"name\": \"location\", \"displayName\": \"Location\", \"type\": \"TEXT\", \"required\": true },\n { \"name\": \"image\", \"displayName\": \"Image\", \"type\": \"IMAGE\", \"required\": false }\n ]\n}\n```\n\n**New Preact section (StoresSection/index.tsx):**\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport default function StoresSection({ stores, ...props }: Props) {\n return (\n <section className=\"stores-section\">\n <IkasComponentRenderer\n id=\"stores\"\n components={stores as any[]}\n parentProps={props}\n />\n </section>\n );\n}\n```\n\n**New Preact child (StoreCard/index.tsx):**\n```tsx\nimport { getDefaultSrc } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport default function StoreCard({ title, location, image }: Props) {\n return (\n <div className=\"store-card\">\n {image && <img src={getDefaultSrc(image)} alt={title} className=\"store-image\" />}\n <h3 className=\"store-title\">{title}</h3>\n <p className=\"store-location\">{location}</p>\n </div>\n );\n}\n```\n\n## Example 2: Simple OBJECT → Flatten into Parent Props\n\nWhen a CUSTOM prop references an OBJECT with just 2-3 simple fields, it's cleaner to flatten them into the parent component.\n\n### Old System\n```json\n// customData:\n{ \"name\": \"TextData\", \"type\": \"OBJECT\", \"nestedData\": [\n { \"key\": \"title\", \"type\": \"TEXT\" },\n { \"key\": \"color\", \"type\": \"COLOR\" }\n]}\n// Component prop:\n{ \"name\": \"textData\", \"type\": \"CUSTOM\", \"customDataId\": \"...\" }\n```\n\n### New System — Just use direct props:\n```json\n{\n \"props\": [\n { \"name\": \"title\", \"displayName\": \"Title\", \"type\": \"TEXT\" },\n { \"name\": \"titleColor\", \"displayName\": \"Title Color\", \"type\": \"COLOR\" }\n ]\n}\n```\n\n## Example 3: Nested DYNAMIC_LIST → Nested COMPONENT_LIST\n\nWhen customData has a DYNAMIC_LIST inside an OBJECT (e.g., menu links with sub-links), you need nested component relationships.\n\n### Old System\n```json\n// MenLink OBJECT has: mainlink (LINK), sublinks (DYNAMIC_LIST of AltLink)\n// AltLink OBJECT has: links (LIST_OF_LINK), cols (SLIDER)\n```\n\n### New System\nCreate three levels:\n1. **NavSection** (section) — has `menuLinks` as COMPONENT_LIST\n2. **MenuLink** (component) — has `mainLink` (LINK), `subLinks` (COMPONENT_LIST)\n3. **SubLink** (component) — has `links` (LIST_OF_LINK), `cols` (NUMBER)\n\nEach level is a registered component in ikas.config.json with its own props.\n\n## Example 4: ENUM customData → ENUM prop\n\n### Old System\n```json\n// customData:\n{ \"name\": \"Input Type\", \"type\": \"ENUM\", \"enumOptions\": [\n { \"displayName\": \"Email\", \"value\": \"email\" },\n { \"displayName\": \"Text\", \"value\": \"text\" },\n { \"displayName\": \"Textarea\", \"value\": \"textarea\" }\n]}\n```\n\n### New System\n```bash\n# First create the enum:\nnpx ikas-component config add-enum --name \"InputType\" --options '{\"Email\":\"email\",\"Text\":\"text\",\"Textarea\":\"textarea\"}'\n# Returns: { \"enumId\": \"aBcDeFgH\" }\n\n# Then use in prop:\n{ \"name\": \"inputType\", \"type\": \"ENUM\", \"enumTypeId\": \"aBcDeFgH\" }\n```\n\n## IMPORTANT: Two Ways to Render Lists — COMPONENT_LIST vs Data Props\n\nThere are two fundamentally different patterns for rendering lists of items. Choosing the wrong one is a common migration mistake.\n\n### Pattern 1: COMPONENT_LIST — Store Owner Hand-Picks Items\n\nUse `COMPONENT_LIST` when the store owner should **manually select and arrange** individual items in the editor. Each item is a separate child component instance.\n\n**Example:** A curated product showcase where the merchant picks 4 featured products.\n- Parent section has `products` prop with type `COMPONENT_LIST`\n- Child **ProductCard** component (registered in ikas.config.json) has a `PRODUCT` prop\n- Store owner drags ProductCard instances into the slot and picks a product for each\n\n**For every COMPONENT_LIST prop, ensure a corresponding child component exists:**\n- Product showcase → **ProductCard** child (PRODUCT prop)\n- Category grid → **CategoryCard** child (CATEGORY prop)\n- Blog highlights → **BlogCard** child (BLOG prop)\n- Comments/reviews → **CommentCard** child (TEXT props for name, comment, rating)\n- Logo slider → **LogoItem** child (IMAGE prop)\n\nThe child component must be:\n1. Registered in `ikas.config.json` (via `config add-component --type component`)\n2. Listed in the parent's `filteredComponentIds`\n3. Capable of rendering a **single item**\n\nIf the child already exists from a previous migration, reuse it — just add its ID to `filteredComponentIds`.\n\n### Pattern 2: PRODUCT_LIST / BLOG_LIST / CATEGORY_LIST — Data-Driven Rendering\n\nUse data prop types (`PRODUCT_LIST`, `BLOG_LIST`, `CATEGORY_LIST`, etc.) when items come from **dynamic data** — filtered, sorted, paginated, or automatically populated. The section renders items **internally** by mapping over the data.\n\n**Example:** A category page that shows all products in a category with filters and pagination.\n- Section has `products` prop with type `PRODUCT_LIST`\n- The section's `index.tsx` maps over `products.data` and renders each product using a ProductCard component\n\n```tsx\n// Section with PRODUCT_LIST — renders items internally\nimport ProductCard from \"../ProductCard\";\nimport { Props } from \"./types\";\n\nexport function CategoryPage({ products, ...props }: Props) {\n return (\n <section>\n <div className=\"product-grid\">\n {products?.data?.map((product, i) => (\n <ProductCard key={i} product={product} />\n ))}\n </div>\n </section>\n );\n}\nexport default CategoryPage;\n```\n\n### A Card Component Can Serve Both Patterns\n\nA single **registered** component like ProductCard can be used in BOTH ways:\n1. **In a COMPONENT_LIST slot** — the store owner drags ProductCard instances into a curated grid and picks a product for each\n2. **Imported internally** — a data-driven section (CategoryPage, SearchPage) imports ProductCard directly and maps over `PRODUCT_LIST.data` to render each item\n\nThis means ProductCard should be a **registered component** in `ikas.config.json` (not just a sub-component), so it's available for both use cases. The same applies to BlogCard, CategoryCard, etc.\n\n### Decision Guide: Which Pattern to Use\n\n| Scenario | Pattern | Prop Type | Item Renderer |\n|----------|---------|-----------|---------------|\n| Merchant hand-picks items (curated grid, featured products) | COMPONENT_LIST | `COMPONENT_LIST` | Registered child component via IkasComponentRenderer |\n| Data-driven list (category page, search results, blog archive) | Data prop | `PRODUCT_LIST` / `BLOG_LIST` / `CATEGORY_LIST` | Same registered component, imported directly and rendered via `.data.map()` |\n| Merchant arranges custom content (slides, tabs, FAQ items) | COMPONENT_LIST | `COMPONENT_LIST` | Registered child component via IkasComponentRenderer |\n\n**Key distinction:** COMPONENT_LIST = store owner controls which items appear and their order. Data props = data (filters, categories, search) controls what appears. **The card component itself is the same in both cases** — it's a registered component that can be placed in slots OR imported for internal rendering.\n\n## Naming Convention for Child Components\n\nWhen decomposing an old component into section + children:\n- Parent section: `{OldComponentName}Section` (e.g., `StoresSection`)\n- Child component: `{OldTypescriptName}` or `{OldComponentName}{ItemName}` (e.g., `StoreCard`)\n- Use `filteredComponentIds` on the COMPONENT_LIST prop to restrict which children can be placed there\n\n## Common Patterns\n\n### Slider/Carousel items\nOld: CUSTOM (DYNAMIC_LIST) with image + text per slide\nNew: Section with COMPONENT_LIST + SlideItem child component\n\n### Tab groups with content\nOld: CUSTOM (DYNAMIC_LIST) with tab name + content per tab\nNew: Section with COMPONENT_LIST + TabItem child component\n\n### Footer link columns\nOld: CUSTOM (DYNAMIC_LIST) with title + links per column\nNew: Section with COMPONENT_LIST + FooterColumn child component\n\n### Comments/Reviews\nOld: CUSTOM (DYNAMIC_LIST) with star + name + comment per review\nNew: Section with COMPONENT_LIST + ReviewItem child component",
20
- "tags": ["migration", "CUSTOM", "customData", "DYNAMIC_LIST", "OBJECT", "STATIC_LIST", "COMPONENT_LIST", "child-component", "conversion"]
35
+ "content": "The old system's `CUSTOM` prop type + `customData` definitions are the most complex part of any migration. Each old customData entry takes one of two paths in the new system — and the path is NOT a 1:1 mechanical conversion. **You must decide per type.**\n\n## The Heuristic\n\n> **Flat scalar set** (e.g. `\"left\" | \"right\" | \"center\"`) → new-system **enum prop** via `config add-enum`.\n>\n> **Structured record** (multi-field, especially if used as a list) → new-system **component** via `config add-component` + COMPONENT_LIST wiring on the parent.\n\nThe old system uses `customData` for both — a `Position` enum and a `Slide` record are both stored in `theme.customData[]`. The new system splits them: enums become a prop type, structured records become their own components. Old themes that flatten a record into an enum-like wrapper (e.g. a single-field customData with an image inside) need to be promoted into proper components in the new system.\n\n## When you call `get_section_migration_plan`\n\nThe MCP scans each prop and emits a **\"Custom Data Decisions to Make\"** callout for every prop that references a customData type. The callout shows the shape and the MCP's default classification. **Verify each default against the actual data semantics** — the MCP only sees shape, not usage. Log every final decision in `MIGRATION.md` under `## Custom Data Decisions`.\n\n## Worked Example 1: `Position` → enum prop\n\nA classic enum case. The old customData is a flat scalar set.\n\n### Old System\n\n```json\n// customData entry:\n{\n \"id\": \"...\",\n \"name\": \"Position\",\n \"type\": \"ENUM\",\n \"enumOptions\": [\n { \"displayName\": \"Left\", \"value\": \"left\" },\n { \"displayName\": \"Right\", \"value\": \"right\" },\n { \"displayName\": \"Center\", \"value\": \"center\" }\n ]\n}\n// Component prop:\n{ \"name\": \"alignment\", \"type\": \"CUSTOM\", \"customDataId\": \"...\" }\n```\n\n### New System (correct path: enum prop)\n\n```bash\nnpx ikas-component config add-enum --name \"Position\" --options '{\"Left\":\"left\",\"Right\":\"right\",\"Center\":\"center\"}'\n# → { \"success\": true, \"enumId\": \"enm_abc123\" }\n```\n\nThen on the parent section:\n\n```bash\nnpx ikas-component config add-prop --component <section-id> --name \"alignment\" --type ENUM --enumTypeId enm_abc123\n```\n\nIn TSX:\n\n```tsx\n<div style={{ textAlign: alignment }}>...</div>\n// `alignment` is a string with value \"left\" | \"right\" | \"center\"\n```\n\n## Worked Example 2: `Slide` → component + COMPONENT_LIST\n\nA repeating structured record. Multiple fields per item, used as a list.\n\n### Old System\n\n```json\n// customData entry:\n{\n \"name\": \"Slide\",\n \"type\": \"DYNAMIC_LIST\",\n \"nestedData\": [{\n \"type\": \"OBJECT\",\n \"nestedData\": [\n { \"key\": \"image\", \"type\": \"IMAGE\", \"name\": \"Image\" },\n { \"key\": \"title\", \"type\": \"TEXT\", \"name\": \"Title\" },\n { \"key\": \"link\", \"type\": \"LINK\", \"name\": \"Link\" }\n ]\n }]\n}\n// HeroSlider prop:\n{ \"name\": \"slides\", \"type\": \"CUSTOM\", \"customDataId\": \"...\" }\n```\n\n### New System (correct path: component + COMPONENT_LIST)\n\n```bash\n# 1. Create the Slide component\nnpx ikas-component config add-component --name \"Slide\" --type component \\\n --props '[{\"name\":\"image\",\"displayName\":\"Image\",\"type\":\"IMAGE\"},{\"name\":\"title\",\"displayName\":\"Title\",\"type\":\"TEXT\"},{\"name\":\"link\",\"displayName\":\"Link\",\"type\":\"LINK\"}]'\n# → { \"componentId\": \"cc_slide_xyz\" }\n\n# 2. Wire it into HeroSlider via COMPONENT_LIST + filteredComponentIds\nnpx ikas-component config add-component --name \"HeroSlider\" --type section \\\n --props '[{\"name\":\"slides\",\"displayName\":\"Slides\",\"type\":\"COMPONENT_LIST\",\"filteredComponentIds\":[\"cc_slide_xyz\"]}]'\n```\n\nIn TSX (HeroSlider renders Slide instances via IkasComponentRenderer):\n\n```tsx\n<IkasComponentRenderer id=\"slides\" components={slides as any[]} parentProps={props} />\n```\n\nIn TSX (Slide is a self-contained component with its three props):\n\n```tsx\nexport default function Slide({ image, title, link }: Props) {\n return (\n <a href={link?.href}>\n {image && <img src={getDefaultSrc(image)} alt={title} />}\n <h3>{title}</h3>\n </a>\n );\n}\n```\n\n## Worked Example 3: `MenuItem` (mega-menu) → component, NOT LIST_OF_LINK\n\nWhen the old theme has navigation links with **per-link metadata** (image, color, icon, submenu layout columns), `LIST_OF_LINK` cannot represent it. `IkasNavigationLink` only has `href`, `label`, `subLinks`, `openInNewTab`, `itemId`, `type` — there is **no slot for an image or any custom field**.\n\nThe correct conversion is the same pattern as `Slide`: build a dedicated component (`MenuItem`) with the per-link fields, then render an array of them via COMPONENT_LIST on the parent header/navbar.\n\n### Old System\n\n```json\n// customData entry:\n{\n \"name\": \"MenuItem\",\n \"type\": \"DYNAMIC_LIST\",\n \"nestedData\": [{\n \"type\": \"OBJECT\",\n \"nestedData\": [\n { \"key\": \"image\", \"type\": \"IMAGE\", \"name\": \"Image\" },\n { \"key\": \"title\", \"type\": \"TEXT\", \"name\": \"Title\" },\n { \"key\": \"link\", \"type\": \"LINK\", \"name\": \"Link\" }\n ]\n }]\n}\n// Header prop:\n{ \"name\": \"menuItems\", \"type\": \"CUSTOM\", \"customDataId\": \"...\" }\n```\n\n### New System (correct path: MenuItem component)\n\n```bash\nnpx ikas-component config add-component --name \"MenuItem\" --type component \\\n --props '[{\"name\":\"image\",\"displayName\":\"Image\",\"type\":\"IMAGE\"},{\"name\":\"title\",\"displayName\":\"Title\",\"type\":\"TEXT\"},{\"name\":\"link\",\"displayName\":\"Link\",\"type\":\"LINK\"}]'\n# → { \"componentId\": \"cc_menu_item_xyz\" }\n```\n\nThen wire into the Header section as a `COMPONENT_LIST` prop with `filteredComponentIds: [\"cc_menu_item_xyz\"]`.\n\n### Why NOT `LIST_OF_LINK`?\n\n```tsx\n// IkasNavigationLink — what LIST_OF_LINK gives you:\ntype IkasNavigationLink = {\n href: string;\n label: string;\n subLinks: IkasNavigationLink[];\n itemId: string;\n type: string;\n openInNewTab: boolean;\n};\n```\n\n**There is no `image` field.** If you flatten a mega-menu into `LIST_OF_LINK` you silently drop the image, color, custom icon, column count, and every other non-link field. Always prefer a `MenuItem`-style component when the old data is richer than href + label.\n\n## Supporting Cases (variations on the same patterns)\n\n### Simple OBJECT with ≤3 simple fields → flatten into parent props\n\nWhen a CUSTOM prop references an OBJECT with just 2-3 simple fields and is NOT used as a list, flattening into the parent's direct props is cleaner than building a child component:\n\n```json\n// Old customData OBJECT: { title: TEXT, color: COLOR }\n// Old prop: { \"name\": \"textData\", \"type\": \"CUSTOM\", \"customDataId\": \"...\" }\n\n// New: just expose two direct props on the parent\n[\n { \"name\": \"title\", \"displayName\": \"Title\", \"type\": \"TEXT\" },\n { \"name\": \"titleColor\", \"displayName\": \"Title Color\", \"type\": \"COLOR\" }\n]\n```\n\nThis is the **only** place where customData becomes neither an enum nor a component — purely because the old wrapping was an unnecessary abstraction.\n\n### Nested DYNAMIC_LIST inside an OBJECT → nested components\n\nE.g. a NavLink with `mainLink` (LINK) + `subLinks` (DYNAMIC_LIST of SubLink). Build a component per level (NavLink, SubLink), each with its own props, and nest COMPONENT_LIST slots.\n\n### STATIC_LIST → still a component\n\n`STATIC_LIST` differs from `DYNAMIC_LIST` only in editor UX (fixed count vs add/remove). In the new system, both are COMPONENT_LIST with a child component.\n\n## Two Ways to Render Lists — COMPONENT_LIST vs Data Props\n\nChoosing the wrong one is a common migration mistake.\n\n### Pattern 1: COMPONENT_LIST — store owner hand-picks items\n\nUse `COMPONENT_LIST` when the store owner should **manually select and arrange** individual items in the editor. Each item is a separate child component instance.\n\n**Example:** A curated product showcase where the merchant picks 4 featured products.\n- Parent section has `products` prop with type `COMPONENT_LIST`\n- Child **ProductCard** component (registered in ikas.config.json) has a `PRODUCT` prop\n- Store owner drags ProductCard instances into the slot and picks a product for each\n\nFor every COMPONENT_LIST prop, ensure a corresponding child component exists:\n- Product showcase → **ProductCard** child (PRODUCT prop)\n- Category grid → **CategoryCard** child (CATEGORY prop)\n- Blog highlights → **BlogCard** child (BLOG prop)\n- Mega menu → **MenuItem** child (LINK + IMAGE + TEXT props, per Example 3)\n- Slider/Carousel → **Slide** child (per Example 2)\n- Logo strip → **LogoItem** child (IMAGE prop)\n\nThe child component must be:\n1. Registered in `ikas.config.json` (via `config add-component --type component`)\n2. Listed in the parent's `filteredComponentIds`\n3. Capable of rendering a single item\n\n### Pattern 2: PRODUCT_LIST / BLOG_LIST / CATEGORY_LIST — data-driven rendering\n\nUse data prop types when items come from **dynamic data** — filtered, sorted, paginated, or automatically populated. The section renders items internally by mapping over the data.\n\n```tsx\nexport function CategoryPage({ products, ...props }: Props) {\n return (\n <section>\n <div className=\"product-grid\">\n {products?.data?.map((product, i) => (\n <ProductCard key={i} product={product} />\n ))}\n </div>\n </section>\n );\n}\n```\n\n### A Card Component Can Serve Both Patterns\n\nA single **registered** component like ProductCard can be used in BOTH ways: in a COMPONENT_LIST slot (merchant drags instances) AND imported directly into a data-driven section (mapped over `.data`). Register it once; reuse everywhere.\n\n### Decision Guide\n\n| Scenario | Pattern | Prop Type | Item Renderer |\n|----------|---------|-----------|---------------|\n| Merchant hand-picks items (curated grid, featured, mega-menu) | COMPONENT_LIST | `COMPONENT_LIST` | Registered child component via IkasComponentRenderer |\n| Data-driven list (category page, search results, blog archive) | Data prop | `PRODUCT_LIST` / `BLOG_LIST` / `CATEGORY_LIST` | Same registered component, imported directly and rendered via `.data.map()` |\n\n## Naming Convention for Child Components\n\n- Parent section: `{OldComponentName}Section` (e.g., `StoresSection`, `HeroSliderSection`)\n- Child component: `{ItemTypescriptName}` or `{ParentName}{ItemName}` (e.g., `StoreCard`, `Slide`, `MenuItem`)\n- Use `filteredComponentIds` on the COMPONENT_LIST prop to restrict which child components can be placed there\n\n## Logging Your Decision\n\nAfter every customData decision, append a row to `MIGRATION.md` under `## Custom Data Decisions`:\n\n```\n- `SlideData` → component `slide` (2026-05-11) — structured record {image, link, title}; wired into HeroSlider via COMPONENT_LIST.\n- `Position` → enum `Position` (2026-05-11) — flat scalar set left/right/center; enumId: enm_abc123.\n- `MenuItemData` → component `menu-item` (2026-05-12) — mega-menu links with image + title; LIST_OF_LINK would have dropped the image.\n```\n\nThis is how future sessions (and you, later) know which path was taken and avoid re-deciding inconsistently.\n",
36
+ "tags": [
37
+ "migration",
38
+ "CUSTOM",
39
+ "customData",
40
+ "DYNAMIC_LIST",
41
+ "OBJECT",
42
+ "STATIC_LIST",
43
+ "COMPONENT_LIST",
44
+ "child-component",
45
+ "conversion"
46
+ ]
21
47
  },
22
48
  "library-replacements": {
23
49
  "title": "Library Replacement Patterns",
24
50
  "description": "How to replace common third-party libraries with vanilla Preact + CSS implementations",
25
51
  "content": "The new code-component system does not support external libraries. Here's how to replace each common library found in old themes.\n\n## swiper → CSS Scroll-Snap Carousel\n\n**What it does:** Touch-enabled carousel/slider with autoplay, pagination, navigation.\n\n**Replacement pattern:**\n```tsx\nimport { useRef, useState, useEffect } from \"preact/hooks\";\n\nfunction Carousel({ children }: { children: any[] }) {\n const trackRef = useRef<HTMLDivElement>(null);\n const [current, setCurrent] = useState(0);\n\n useEffect(() => {\n const track = trackRef.current;\n if (!track) return;\n const handleScroll = () => {\n const index = Math.round(track.scrollLeft / track.clientWidth);\n setCurrent(index);\n };\n track.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => track.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n const goTo = (index: number) => {\n trackRef.current?.scrollTo({ left: index * trackRef.current.clientWidth, behavior: \"smooth\" });\n };\n\n return (\n <div className=\"carousel\">\n <div ref={trackRef} className=\"carousel-track\">\n {children}\n </div>\n <div className=\"carousel-dots\">\n {children.map((_, i) => (\n <button\n key={i}\n className={`carousel-dot ${i === current ? \"active\" : \"\"}`}\n onClick={() => goTo(i)}\n />\n ))}\n </div>\n </div>\n );\n}\n```\n\n**CSS:**\n```css\n.carousel-track {\n display: flex;\n overflow-x: auto;\n scroll-snap-type: x mandatory;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n}\n.carousel-track::-webkit-scrollbar { display: none; }\n.carousel-track > * {\n flex: 0 0 100%;\n scroll-snap-align: start;\n}\n.carousel-dot {\n width: 8px; height: 8px; border-radius: 50%;\n background: #ccc; border: none; cursor: pointer;\n}\n.carousel-dot.active { background: #333; }\n```\n\n**For autoplay**, add a `useEffect` with `setInterval` that calls `goTo((current + 1) % count)`.\n\n**For `slidesPerView: \"auto\"`**, remove `flex: 0 0 100%` and let children size themselves.\n\n## @headlessui/react → Custom Preact Components\n\n**Disclosure (accordion):**\n```tsx\nimport { useState } from \"preact/hooks\";\n\nfunction Disclosure({ title, children }: { title: string; children: any }) {\n const [open, setOpen] = useState(false);\n return (\n <div>\n <button onClick={() => setOpen(!open)} className=\"disclosure-btn\">\n {title}\n <span className={`arrow ${open ? \"open\" : \"\"}`}>▸</span>\n </button>\n {open && <div className=\"disclosure-panel\">{children}</div>}\n </div>\n );\n}\n```\n\n**Dialog (modal):**\n```tsx\nimport { useEffect, useRef } from \"preact/hooks\";\n\nfunction Dialog({ open, onClose, children }: { open: boolean; onClose: () => void; children: any }) {\n const overlayRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (open) document.body.style.overflow = \"hidden\";\n else document.body.style.overflow = \"\";\n return () => { document.body.style.overflow = \"\"; };\n }, [open]);\n\n if (!open) return null;\n return (\n <div ref={overlayRef} className=\"dialog-overlay\" onClick={(e) => { if (e.target === overlayRef.current) onClose(); }}>\n <div className=\"dialog-content\">{children}</div>\n </div>\n );\n}\n```\n\n## recharts → SVG or CSS Charts\n\nFor simple bar/line charts, use inline SVG. For complex charts, consider CSS-based bar charts:\n```tsx\nfunction BarChart({ data }: { data: { label: string; value: number }[] }) {\n const max = Math.max(...data.map(d => d.value));\n return (\n <div className=\"bar-chart\">\n {data.map((d, i) => (\n <div key={i} className=\"bar-item\">\n <div className=\"bar\" style={{ height: `${(d.value / max) * 100}%` }} />\n <span className=\"bar-label\">{d.label}</span>\n </div>\n ))}\n </div>\n );\n}\n```\n\n## react-player → Native Video/Iframe\n\n```tsx\nfunction VideoPlayer({ url, poster }: { url: string; poster?: string }) {\n if (url.includes(\"youtube.com\") || url.includes(\"youtu.be\")) {\n const videoId = url.match(/(?:v=|youtu\\.be\\/)([^&]+)/)?.[1];\n return <iframe src={`https://www.youtube.com/embed/${videoId}`} allowFullScreen className=\"video-iframe\" />;\n }\n if (url.includes(\"vimeo.com\")) {\n const videoId = url.match(/vimeo\\.com\\/(\\d+)/)?.[1];\n return <iframe src={`https://player.vimeo.com/video/${videoId}`} allowFullScreen className=\"video-iframe\" />;\n }\n return <video src={url} poster={poster} controls className=\"video-native\" />;\n}\n```\n\n## react-simple-star-rating → CSS Star Rating\n\n```tsx\nfunction StarRating({ rating, max = 5 }: { rating: number; max?: number }) {\n return (\n <div className=\"star-rating\">\n {Array.from({ length: max }, (_, i) => (\n <span key={i} className={`star ${i < Math.round(rating) ? \"filled\" : \"\"}`}>★</span>\n ))}\n </div>\n );\n}\n```\n\n```css\n.star { color: #ddd; font-size: 1.2em; }\n.star.filled { color: #ffc107; }\n```\n\n## react-slider / react-compound-slider → Native Range Input\n\n```tsx\nfunction RangeSlider({ min, max, value, onChange }: { min: number; max: number; value: number; onChange: (v: number) => void }) {\n return (\n <input\n type=\"range\"\n min={min}\n max={max}\n value={value}\n onInput={(e) => onChange(Number((e.target as HTMLInputElement).value))}\n className=\"range-slider\"\n />\n );\n}\n```\n\n## react-hot-toast → Simple Toast\n\n```tsx\nimport { useState, useEffect } from \"preact/hooks\";\n\nfunction Toast({ message, duration = 3000, onClose }: { message: string; duration?: number; onClose: () => void }) {\n useEffect(() => {\n const timer = setTimeout(onClose, duration);\n return () => clearTimeout(timer);\n }, [duration, onClose]);\n\n return <div className=\"toast\">{message}</div>;\n}\n```\n\n```css\n.toast {\n position: fixed; bottom: 20px; right: 20px;\n background: #333; color: white; padding: 12px 24px;\n border-radius: 8px; z-index: 9999;\n animation: slideIn 0.3s ease;\n}\n@keyframes slideIn { from { transform: translateY(20px); opacity: 0; } }\n```\n\n## react-fast-marquee → CSS Marquee\n\n```css\n.marquee-container { overflow: hidden; }\n.marquee-track {\n display: flex; width: max-content;\n animation: marquee 20s linear infinite;\n}\n@keyframes marquee {\n 0% { transform: translateX(0); }\n 100% { transform: translateX(-50%); }\n}\n```\nDuplicate the content so it loops seamlessly.\n\n## react-indiana-drag-scroll → CSS Overflow Scroll\n\n```css\n.drag-scroll {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: none;\n cursor: grab;\n}\n.drag-scroll::-webkit-scrollbar { display: none; }\n```\n\n## react-simple-typewriter → setInterval Typewriter\n\n```tsx\nimport { useState, useEffect } from \"preact/hooks\";\n\nfunction Typewriter({ words, speed = 100 }: { words: string[]; speed?: number }) {\n const [text, setText] = useState(\"\");\n const [wordIndex, setWordIndex] = useState(0);\n const [charIndex, setCharIndex] = useState(0);\n const [deleting, setDeleting] = useState(false);\n\n useEffect(() => {\n const word = words[wordIndex];\n const timer = setTimeout(() => {\n if (!deleting) {\n setText(word.slice(0, charIndex + 1));\n if (charIndex + 1 === word.length) {\n setTimeout(() => setDeleting(true), 1500);\n } else {\n setCharIndex(c => c + 1);\n }\n } else {\n setText(word.slice(0, charIndex));\n if (charIndex === 0) {\n setDeleting(false);\n setWordIndex((i) => (i + 1) % words.length);\n } else {\n setCharIndex(c => c - 1);\n }\n }\n }, deleting ? speed / 2 : speed);\n return () => clearTimeout(timer);\n }, [charIndex, deleting, wordIndex, words, speed]);\n\n return <span>{text}<span className=\"cursor\">|</span></span>;\n}\n```\n\n## react-timer-hook → useEffect Countdown\n\n```tsx\nimport { useState, useEffect } from \"preact/hooks\";\n\nfunction useCountdown(targetDate: Date) {\n const [timeLeft, setTimeLeft] = useState(getTimeLeft(targetDate));\n\n useEffect(() => {\n const interval = setInterval(() => setTimeLeft(getTimeLeft(targetDate)), 1000);\n return () => clearInterval(interval);\n }, [targetDate]);\n\n return timeLeft;\n}\n\nfunction getTimeLeft(target: Date) {\n const diff = Math.max(0, target.getTime() - Date.now());\n return {\n days: Math.floor(diff / 86400000),\n hours: Math.floor((diff % 86400000) / 3600000),\n minutes: Math.floor((diff % 3600000) / 60000),\n seconds: Math.floor((diff % 60000) / 1000),\n };\n}\n```\n\n## date-fns → Native Intl / Inline Utils\n\n```tsx\nfunction formatDate(date: Date | string, locale = \"tr-TR\"): string {\n return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"long\", year: \"numeric\" }).format(new Date(date));\n}\n\nfunction formatRelative(date: Date | string): string {\n const diff = Date.now() - new Date(date).getTime();\n const days = Math.floor(diff / 86400000);\n if (days === 0) return \"Today\";\n if (days === 1) return \"Yesterday\";\n if (days < 7) return `${days} days ago`;\n return formatDate(date);\n}\n```\n\n## slugify → Inline Slugify\n\n```tsx\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n```\n\n## classnames → Template Literals\n\nOld: `classnames('base', { active: isActive, hidden: !visible })`\nNew: `` `base ${isActive ? 'active' : ''} ${!visible ? 'hidden' : ''}`.trim() ``\n\nOr a simple helper:\n```tsx\nfunction cn(...classes: (string | false | undefined | null)[]): string {\n return classes.filter(Boolean).join(\" \");\n}\n```\n\n## tailwindcss → Plain CSS\n\nConvert Tailwind utility classes to equivalent CSS. Common patterns:\n\n| Tailwind | CSS |\n|----------|-----|\n| `flex` | `display: flex` |\n| `grid grid-cols-2` | `display: grid; grid-template-columns: repeat(2, 1fr)` |\n| `gap-4` | `gap: 1rem` |\n| `p-6` | `padding: 1.5rem` |\n| `mt-4` | `margin-top: 1rem` |\n| `text-center` | `text-align: center` |\n| `text-[20px]` | `font-size: 20px` |\n| `font-light` | `font-weight: 300` |\n| `rounded-lg` | `border-radius: 0.5rem` |\n| `hidden lg:block` | `display: none; @media (min-width: 1024px) { display: block; }` |\n| `hover:opacity-80` | `.class:hover { opacity: 0.8; }` |\n| `transition-all` | `transition: all 0.3s ease` |\n| `absolute inset-0` | `position: absolute; top: 0; right: 0; bottom: 0; left: 0` |\n| `overflow-hidden` | `overflow: hidden` |\n| `prose` | Custom rich text styles (font sizes, line heights, margins for h1-h6, p, ul, ol) |\n\nTailwind breakpoints: `sm` = 640px, `md` = 768px, `lg` = 1024px, `xl` = 1280px, `2xl` = 1536px.\n\n## @react-pdf/renderer\n\nPDF generation cannot be replicated in code components. If the old theme generates PDFs, this functionality must be dropped or handled externally.\n\n## @heroicons/react → Inline SVG\n\nReplace icon components with inline SVG. Copy the SVG paths from the Heroicons website and create simple components:\n```tsx\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" className={className} width=\"20\" height=\"20\">\n <path fillRule=\"evenodd\" d=\"M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z\" clipRule=\"evenodd\" />\n </svg>\n );\n}\n```",
26
- "tags": ["migration", "libraries", "swiper", "headlessui", "recharts", "react-player", "tailwind", "classnames", "marquee", "typewriter", "toast", "slider", "star-rating", "replacement", "vanilla"]
52
+ "tags": [
53
+ "migration",
54
+ "libraries",
55
+ "swiper",
56
+ "headlessui",
57
+ "recharts",
58
+ "react-player",
59
+ "tailwind",
60
+ "classnames",
61
+ "marquee",
62
+ "typewriter",
63
+ "toast",
64
+ "slider",
65
+ "star-rating",
66
+ "replacement",
67
+ "vanilla"
68
+ ]
27
69
  },
28
70
  "react-to-preact": {
29
71
  "title": "React to Preact Conversion Patterns",
30
72
  "description": "Code patterns for converting old React components to new Preact code components",
31
73
  "content": "## CRITICAL: Two Completely Different Systems\n\n**`@ikas/storefront` and `@ikas/bp-storefront` are entirely separate packages.** Types with the same name are NOT the same types — they come from different packages with different properties. Never copy old `@ikas/storefront` type usage patterns into new code. Always use the MCP tools (`get_type_definition`, `get_function_doc`, `get_model_guide`) to look up the correct API in the new system.\n\n## Import Changes\n\n### Remove React import\nOld: `import React from 'react';` or `import * as React from 'react';`\nNew: Remove entirely. Preact JSX is auto-imported.\n\n### Hook imports\nOld: `import { useState, useEffect, useRef, useMemo, useCallback } from 'react';`\nNew: `import { useState, useEffect, useRef, useMemo, useCallback } from 'preact/hooks';`\n\n### Storefront imports\nOld: `import { Image, ... } from '@ikas/storefront';`\nNew: `import { getDefaultSrc, ... } from '@ikas/bp-storefront';`\n\n**WARNING:** Do not assume that because `IkasImage` exists in both packages, it has the same shape. The old `IkasImage` from `@ikas/storefront` and the new `IkasImage` from `@ikas/bp-storefront` are different types. Always check the new type definition via `get_type_definition('IkasImage')` before using it.\n\nThe old `<Image>` component from `@ikas/storefront` does not exist in the new system. Use native `<img>` with `getDefaultSrc()`:\n```tsx\n// Old:\nimport { Image } from '@ikas/storefront';\n<Image image={myImage} layout=\"fill\" objectFit=\"cover\" sizes=\"300px\" />\n\n// New:\nimport { getDefaultSrc } from '@ikas/bp-storefront';\n<img src={getDefaultSrc(myImage)} alt=\"\" className=\"my-image\" />\n```\n\nFor responsive images, use `getSrc()` with width parameter or `createMediaSrcset()`.\n\n### Observer pattern\nOld: `import { observer } from 'mobx-react-lite'; export default observer(MyComponent);`\nNew: Remove for root components. Only use on sub-components:\n```tsx\n// Root component (in src/components/) — NO observer:\nexport default function MySection(props: Props) { ... }\n\n// Sub-component (in src/sub-components/) — observer OK:\nimport { observer } from '@ikas/component-utils';\nfunction CartCounter() { ... }\nexport default observer(CartCounter);\n```\n\n## Component Declaration\n\nOld:\n```tsx\nconst MyComponent = ({ title, image }: MyComponentProps) => { return (...); };\nexport default observer(MyComponent);\n```\n\nNew:\n```tsx\nimport { Props } from './types';\n\nexport function MyComponent({ title, image }: Props) { return (...); }\nexport default MyComponent;\n```\n\n**CRITICAL: Components need BOTH a named export AND a default export.** The CLI auto-generates a barrel file (`src/components/index.ts`) that uses named imports: `export { MyComponent } from './MyComponent/index'`. If you only do `export default function MyComponent`, the barrel import will fail with type errors. Always declare the function with `export function Name(...)` and then add `export default Name;` below it.\n\nKey changes:\n- Named function export AND default export (both required)\n- Props type imported from `./types` (auto-generated by CLI)\n- No `observer()` wrapper on root components\n\n## Type Changes\n\n| Old Type | New Type | Notes |\n|----------|----------|-------|\n| `React.CSSProperties` | `JSX.CSSProperties` | Or just use `Record<string, string>` |\n| `React.FC<Props>` | Remove, use named function | |\n| `React.MouseEvent` | `JSX.TargetedMouseEvent<HTMLElement>` | |\n| `React.ChangeEvent<HTMLInputElement>` | `JSX.TargetedEvent<HTMLInputElement>` | |\n| `React.FormEvent` | `JSX.TargetedEvent<HTMLFormElement>` | |\n| `React.ReactNode` | `ComponentChildren` from `preact` | |\n| `IkasSlider` | `number` | Was `{ value: number }`, now plain number |\n\n## CSS Patterns\n\n### Tailwind → Scoped CSS\nOld: `<div className=\"flex items-center gap-4 p-6 text-center\">`\nNew: `<div className=\"my-container\">` with CSS:\n```css\n.my-container {\n display: flex;\n align-items: center;\n gap: 1rem;\n padding: 1.5rem;\n text-align: center;\n}\n```\n\n### CSS Modules → Scoped CSS\nOld: `import styles from './styles.module.css'; <div className={styles.container}>`\nNew: `<div className=\"container\">` — CSS is auto-scoped via `.cc_{componentId}` prefix.\n\n### Inline styles with CSS custom properties\nOld: `style={{ '--mth': \\`\\${marginTop?.value}px\\` } as React.CSSProperties}`\nNew: `style={{ '--mth': \\`\\${marginTop}px\\` }}` — Note: `marginTop` is now a plain number, not `IkasSlider`.\n\n## IkasSlider → number\n\nThis is one of the most common code changes. Every `IkasSlider` value access needs updating:\n\n```tsx\n// Old (IkasSlider has .value property):\nconst width = logoWidth?.value || 120;\nconst gap = `${spacing?.value || 0}px`;\nstyle={{ '--w': `${item.width?.value}px` }}\n\n// New (plain number):\nconst width = logoWidth || 120;\nconst gap = `${spacing || 0}px`;\nstyle={{ '--w': `${item.width}px` }}\n```\n\n## Image Handling\n\n```tsx\n// Old:\nimport { Image } from '@ikas/storefront';\n<Image image={myImage} layout=\"fill\" objectFit=\"cover\" sizes=\"300px\" />\n<Image image={myImage} layout=\"responsive\" width={300} height={200} />\n\n// New:\nimport { getDefaultSrc, getSrc } from '@ikas/bp-storefront';\n\n// Simple image:\n{myImage && <img src={getDefaultSrc(myImage)} alt=\"\" />}\n\n// With specific size:\n{myImage && <img src={getSrc(myImage, { width: 300 })} alt=\"\" />}\n\n// Background image:\n<div style={{ backgroundImage: myImage ? `url(${getDefaultSrc(myImage)})` : 'none' }} />\n```\n\n## Video Handling\n\n```tsx\n// Old (IkasVideo was often used with react-player):\nimport ReactPlayer from 'react-player';\n<ReactPlayer url={video.url} />\n\n// New (native video or iframe):\n{video && (\n video.type === 'EMBED'\n ? <iframe src={video.url} allowFullScreen />\n : <video src={video.url} controls poster={video.thumbnailUrl} />\n)}\n```\n\n## Event Handling\n\n```tsx\n// Old:\nconst handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setValue(e.target.value);\n};\n\n// New:\nconst handleChange = (e: JSX.TargetedEvent<HTMLInputElement>) => {\n setValue((e.target as HTMLInputElement).value);\n};\n\n// Or simply:\nconst handleInput = (e: Event) => {\n setValue((e.target as HTMLInputElement).value);\n};\n// Use onInput instead of onChange for text inputs in Preact\n```\n\n## dangerouslySetInnerHTML\n\nWorks the same in both:\n```tsx\n<div dangerouslySetInnerHTML={{ __html: richTextContent }} />\n```\n\n## useStore → Direct imports\n\nOld themes used `useStore()` from `@ikas/storefront` for accessing MobX stores:\n```tsx\n// Old:\nconst { cartStore, customerStore } = useStore();\n\n// New — import stores directly:\nimport { cartStore, customerStore } from '@ikas/bp-storefront';\n```\n\n## key prop\n\nSame in both systems. Always use `key` when mapping:\n```tsx\n{items.map((item, i) => <div key={item.id || i}>...</div>)}\n```",
32
- "tags": ["migration", "react", "preact", "observer", "imports", "hooks", "css", "types", "IkasSlider", "Image", "conversion"]
74
+ "tags": [
75
+ "migration",
76
+ "react",
77
+ "preact",
78
+ "observer",
79
+ "imports",
80
+ "hooks",
81
+ "css",
82
+ "types",
83
+ "IkasSlider",
84
+ "Image",
85
+ "conversion"
86
+ ]
33
87
  },
34
88
  "storefront-import-mapping": {
35
89
  "title": "Import Mapping: @ikas/storefront → @ikas/bp-storefront",
36
90
  "description": "Maps old @ikas/storefront imports to new @ikas/bp-storefront equivalents",
37
91
  "content": "## CRITICAL: These Are Completely Different Packages\n\n**`@ikas/storefront` (old) and `@ikas/bp-storefront` (new) are entirely different packages with different implementations.** This is NOT a simple find-and-replace of the package name. Even when types share the same name (e.g., `IkasImage`, `IkasProduct`), they are **different types** with potentially different properties, methods, and behaviors. You must look up each type's actual definition in the new system using `get_type_definition` or `get_model_guide` before using it.\n\n## Package Change\n\nAll imports from `@ikas/storefront` must be changed to `@ikas/bp-storefront`, but you must also verify that the types you're using exist and have the expected shape in the new package.\n\n## Type Mappings (Name Correspondence Only — NOT Identical Types)\n\n| Old Import | New Import | Notes |\n|-----------|-----------|-------|\n| `IkasImage` | `IkasImage` | Same name |\n| `IkasVideo` | `IkasVideo` | Same name |\n| `IkasNavigationLink` | `IkasNavigationLink` | Same name |\n| `IkasSlider` | *(removed)* | Use plain `number` instead |\n| `IkasProduct` | `IkasProduct` | Same name |\n| `IkasProductList` | `IkasProductList` | Same name |\n| `IkasCategory` | `IkasCategory` | Same name |\n| `IkasCategoryList` | `IkasCategoryList` | Same name |\n| `IkasBrand` | `IkasBrand` | Same name |\n| `IkasBlog` | `IkasBlog` | Same name |\n| `IkasBlogList` | `IkasBlogList` | Same name |\n| `IkasBlogCategory` | `IkasBlogCategory` | Same name |\n| `IkasBlogCategoryList` | `IkasBlogCategoryList` | Same name |\n| `IkasAttributeDetail` | `IkasProductAttributeValue` | Renamed |\n| `IkasAttributeList` | `IkasProductAttributeValue[]` | Renamed, now array |\n| `IkasComponentRenderer` | `IkasComponentRenderer` | Same name, different usage pattern |\n\n## Function Mappings\n\n| Old Function | New Function | Notes |\n|-------------|-------------|-------|\n| `Image` component | `getDefaultSrc()`, `getSrc()` | Component replaced with utility functions |\n| `useStore()` | Direct imports (`cartStore`, `customerStore`, etc.) | No more hook wrapper |\n\n## IkasComponentRenderer Changes\n\nOld: Component used as wrapper with children.\nNew: Component used with `components`, `id`, and `parentProps` attributes:\n\n```tsx\n// Old:\nimport { IkasComponentRenderer } from '@ikas/storefront';\n<IkasComponentRenderer>{children}</IkasComponentRenderer>\n\n// New:\nimport { IkasComponentRenderer } from '@ikas/bp-storefront';\n<IkasComponentRenderer\n id=\"my-list\"\n components={myComponentList as any[]}\n parentProps={props}\n/>\n```\n\n## Store Access\n\nOld stores were accessed via `useStore()` hook. New stores are direct imports:\n```tsx\n// Old:\nimport { useStore } from '@ikas/storefront';\nconst { cartStore, customerStore, i18nStore } = useStore();\n\n// New:\nimport { cartStore, customerStore } from '@ikas/bp-storefront';\n// Use directly — they're already MobX observables\n```\n\nUse `search_docs` or `list_functions` on the MCP server to discover all available functions and stores in `@ikas/bp-storefront`.",
38
- "tags": ["migration", "imports", "storefront", "bp-storefront", "IkasSlider", "IkasImage", "useStore", "IkasComponentRenderer"]
92
+ "tags": [
93
+ "migration",
94
+ "imports",
95
+ "storefront",
96
+ "bp-storefront",
97
+ "IkasSlider",
98
+ "IkasImage",
99
+ "useStore",
100
+ "IkasComponentRenderer"
101
+ ]
39
102
  },
40
103
  "theme-json-anatomy": {
41
104
  "title": "Old theme.json Structure",
42
105
  "description": "Explains the structure of old theme.json files for interpreting analyze_old_theme output",
43
106
  "content": "## Top-Level Structure\n\n```json\n{\n \"id\": \"uuid\",\n \"name\": \"Theme Name\",\n \"components\": [...],\n \"pages\": [...],\n \"customData\": [...],\n \"groups\": [...],\n \"settings\": { \"colors\": [...], \"fontFamily\": {...}, \"favicon\": {...}, \"stockPreference\": \"...\" },\n \"languages\": [...]\n}\n```\n\n## components[]\n\nEach component is a reusable UI element:\n```json\n{\n \"id\": \"uuid-or-name\",\n \"dir\": \"ComponentDirName\",\n \"displayName\": \"Human Name\",\n \"description\": \"...\",\n \"type\": \"COMPONENT\",\n \"props\": [...],\n \"defaultPropValues\": { \"propName\": null },\n \"defaultPropValuesTranslations\": { \"locale\": { \"propName\": \"...\" } },\n \"commonPropValues\": {},\n \"translations\": {}\n}\n```\n\n`dir` is the component's directory under `src/components/`. It maps to the React component file at `src/components/{dir}/index.tsx`.\n\n## components[].props[]\n\nEach prop definition:\n```json\n{\n \"id\": \"uuid\",\n \"name\": \"propName\",\n \"displayName\": \"Prop Label\",\n \"description\": \"\",\n \"type\": \"TEXT|BOOLEAN|IMAGE|CUSTOM|SLIDER|...\",\n \"isRequired\": true,\n \"customDataId\": \"uuid-or-null\",\n \"sliderData\": { \"min\": 0, \"max\": 100, \"interval\": 1 },\n \"groupId\": \"uuid-or-empty\",\n \"translations\": {},\n \"excludedFields\": [],\n \"excludedFieldsCategory\": [],\n \"isFacetListNotSent\": false\n}\n```\n\nKey fields:\n- `type` — The prop type (TEXT, BOOLEAN, IMAGE, CUSTOM, SLIDER, etc.)\n- `customDataId` — Only for `type: \"CUSTOM\"`. References a customData entry by ID.\n- `sliderData` — Only for `type: \"SLIDER\"`. Contains min, max, interval.\n- `groupId` — References a group in the `groups[]` array.\n\n## customData[]\n\nReusable data type definitions. Each entry defines a complex data structure:\n\n```json\n{\n \"id\": \"uuid\",\n \"name\": \"Type Name\",\n \"typescriptName\": \"TypeName\",\n \"type\": \"DYNAMIC_LIST|OBJECT|STATIC_LIST|ENUM\",\n \"isRequired\": true,\n \"isRoot\": true,\n \"level\": 0,\n \"nestedData\": [...],\n \"enumOptions\": [...],\n \"sliderData\": {},\n \"translations\": {}\n}\n```\n\n### DYNAMIC_LIST\nA variable-length list. `nestedData` contains one OBJECT entry describing the item shape:\n```json\n{ \"type\": \"DYNAMIC_LIST\", \"nestedData\": [\n { \"type\": \"OBJECT\", \"typescriptName\": \"Item\", \"nestedData\": [\n { \"key\": \"title\", \"type\": \"TEXT\" },\n { \"key\": \"image\", \"type\": \"IMAGE\" }\n ]}\n]}\n```\nThe OBJECT's `nestedData` fields have `key` properties that become TypeScript property names.\n\n### OBJECT\nA structured object with named fields. Each field in `nestedData` has a `key`:\n```json\n{ \"type\": \"OBJECT\", \"nestedData\": [\n { \"key\": \"fieldName\", \"type\": \"TEXT\", \"name\": \"Field Label\" }\n]}\n```\n\n### Nesting\nCustomData can nest arbitrarily. A DYNAMIC_LIST can contain an OBJECT that has fields which are themselves DYNAMIC_LIST or reference other customData via `customDataId`.\n\n## groups[]\n\nProp grouping definitions:\n```json\n{ \"id\": \"uuid\", \"name\": \"Group Name\", \"translations\": {} }\n```\nComponents reference groups via `groupId` on their props.\n\n## settings\n\n```json\n{\n \"colors\": [\n { \"id\": \"uuid\", \"displayName\": \"Color Name\", \"color\": \"#hexvalue\", \"key\": \"--css-var-name\" }\n ],\n \"fontFamily\": { \"name\": \"FontName\", \"variants\": [\"300\", \"regular\", \"500\", \"600\", \"700\"] },\n \"favicon\": { \"id\": \"uuid-or-null\" },\n \"stockPreference\": \"SHOW_ALL|HIDE_OUT_OF_STOCK\"\n}\n```\n\nColors define CSS custom properties (e.g., `--primary`, `--button-bg-1`). These should be converted to CSS variables in `global.css`.\n\n## pages[]\n\nPage definitions with component instances and their prop values. Not needed for component migration but useful for understanding which components are used where.",
44
- "tags": ["migration", "theme-json", "structure", "anatomy", "components", "customData", "groups", "settings", "pages"]
107
+ "tags": [
108
+ "migration",
109
+ "theme-json",
110
+ "structure",
111
+ "anatomy",
112
+ "components",
113
+ "customData",
114
+ "groups",
115
+ "settings",
116
+ "pages"
117
+ ]
45
118
  },
46
119
  "component-decomposition-strategy": {
47
120
  "title": "Component Decomposition Strategy",
48
121
  "description": "How to break old monolithic components into section + child components in the new system",
49
122
  "content": "## Overview\n\nOld themes typically have flat component lists where each component handles everything internally. The new system uses a section → child component hierarchy. Here's how to decompose.\n\n## Rule 1: Every Old Component Becomes a Section\n\nIn the new system, sections are page-level containers. Each old component maps to a `type: \"section\"` in ikas.config.json.\n\nException: If an old component was only used as an internal sub-component (e.g., a reusable ProductCard), it becomes a `type: \"component\"` (child) instead.\n\n## Rule 2: CUSTOM (DYNAMIC_LIST) Props → COMPONENT_LIST + Child\n\nFor each CUSTOM prop that references a DYNAMIC_LIST customData:\n1. Replace the CUSTOM prop with `COMPONENT_LIST` on the parent section\n2. Create a new child component for the list item\n3. Map the OBJECT's nested fields to props on the child component\n4. Use `filteredComponentIds` to restrict which children can be placed in the slot\n\n## Rule 3: Flatten Simple Objects\n\nIf a CUSTOM prop references an OBJECT with ≤3 simple fields (TEXT, BOOLEAN, COLOR, NUMBER), flatten them into direct props on the parent. This avoids unnecessary child components.\n\nExample:\n- Old: `settings: { backgroundColor: string, showTitle: boolean }` via CUSTOM OBJECT\n- New: `backgroundColor` (COLOR prop) + `showTitle` (BOOLEAN prop) directly on section\n\n## Rule 4: Header and Footer\n\nIdentify header/footer components (usually named \"Navbar\", \"Header\", \"Footer\"):\n- Mark the section with `isHeader: true` or `isFooter: true`\n- These sections appear on every page automatically\n\n## Rule 5: Prop Groups\n\nOld `groups[]` array maps to `propGroups` on each component:\n```json\n// Old (global groups array):\n\"groups\": [{ \"id\": \"group-uuid\", \"name\": \"Slider\" }]\n// Component prop: { \"name\": \"delay\", \"groupId\": \"group-uuid\" }\n\n// New (per-component propGroups):\n\"propGroups\": [{ \"id\": \"slider\", \"name\": \"Slider\" }]\n// Component prop: { \"name\": \"delay\", \"groupId\": \"slider\" }\n```\n\n## Rule 6: Shared Sub-Components\n\nIf multiple sections share similar UI elements (buttons, modals, cards), create them in `src/sub-components/` instead of registering them in ikas.config.json.\n\n## Example Decomposition\n\n**Old: ProductGrid component** with props:\n- `products` (CUSTOM → DYNAMIC_LIST of `{ tab: string, products: IkasProductList }`)\n- `showedTags` (CUSTOM → DYNAMIC_LIST of `{ tag: string, color: string }`)\n- `columns` (SLIDER)\n- `gap` (SLIDER)\n\n**New structure:**\n1. **ProductGridSection** (section)\n - `tabs` → COMPONENT_LIST (filteredComponentIds: [\"my-project-product-tab\"])\n - `tags` → COMPONENT_LIST (filteredComponentIds: [\"my-project-product-tag\"])\n - `columns` → NUMBER\n - `gap` → NUMBER\n\n2. **ProductTab** (component)\n - `tabName` → TEXT\n - `products` → PRODUCT_LIST\n\n3. **ProductTag** (component)\n - `tag` → TEXT\n - `color` → COLOR\n - `backgroundColor` → COLOR\n\n## Estimating Child Components\n\nCount the number of CUSTOM props across all components that reference DYNAMIC_LIST customData. Each one typically needs 1 child component. Some may share child components if the structure is identical.",
50
- "tags": ["migration", "decomposition", "section", "component", "COMPONENT_LIST", "header", "footer", "strategy"]
123
+ "tags": [
124
+ "migration",
125
+ "decomposition",
126
+ "section",
127
+ "component",
128
+ "COMPONENT_LIST",
129
+ "header",
130
+ "footer",
131
+ "strategy"
132
+ ]
51
133
  },
52
134
  "complete-project-generation": {
53
135
  "title": "Complete Project Generation Guide",
54
136
  "description": "How to generate a full code-component project from an old theme in one pass",
55
137
  "content": "## Project Structure\n\nThe generated project should follow this structure:\n```\nmy-theme/\n├── src/\n│ ├── global.css\n│ └── components/\n│ ├── HeaderSection/\n│ │ ├── index.tsx\n│ │ ├── types.ts # Auto-generated (don't write manually)\n│ │ └── styles.css\n│ ├── HeroBanner/\n│ │ ├── index.tsx\n│ │ ├── types.ts\n│ │ └── styles.css\n│ ├── SlideItem/ # Child component for hero slides\n│ │ ├── index.tsx\n│ │ ├── types.ts\n│ │ └── styles.css\n│ ├── FooterSection/\n│ │ ├── index.tsx\n│ │ ├── types.ts\n│ │ └── styles.css\n│ └── index.ts # Barrel export\n├── ikas.config.json\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\n## Generation Order\n\n### 1. Generate ikas.config.json\n\nThis is the most critical file. It defines all components and their props.\n\n```json\n{\n \"name\": \"my-theme\",\n \"version\": \"1.0.0\",\n \"globalStyles\": \"./src/global.css\",\n \"components\": [\n {\n \"id\": \"my-theme-header\",\n \"name\": \"HeaderSection\",\n \"type\": \"section\",\n \"isHeader\": true,\n \"entry\": \"./src/components/HeaderSection/index.tsx\",\n \"styles\": \"./src/components/HeaderSection/styles.css\",\n \"props\": [...],\n \"propGroups\": [...]\n }\n ],\n \"customTypes\": [...]\n}\n```\n\nKey rules:\n- Component `id` format: `{projectName}-{component-kebab-case}`\n- Sections use `type: \"section\"`, children use `type: \"component\"` (or omit for default)\n- COMPONENT_LIST props should have `filteredComponentIds` pointing to specific child components\n- Create `customTypes` entries for all ENUM types\n\n### 2. Generate global.css\n\nConvert theme settings to CSS:\n```css\n/* Colors from settings.colors */\n:root {\n --ann-color: #ac8748;\n --fsp: #a9a9a9;\n --primary: #fed9d9;\n --button-bg-1: #000000;\n}\n\n/* Font from settings.fontFamily */\n@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');\n\nbody {\n font-family: 'Quicksand', sans-serif;\n}\n\n/* Common utility styles migrated from old global.css */\n.wrapper {\n max-width: 1700px;\n margin: 0 auto;\n padding: 0 20px;\n}\n\n@media (min-width: 1024px) {\n .wrapper {\n padding: 0 60px;\n }\n}\n```\n\n### 3. Generate Component Source Files\n\nFor each component, generate `index.tsx` and `styles.css`.\n\n**Do NOT generate `types.ts`** — the CLI auto-generates it from ikas.config.json.\n\nEach component should:\n- Import `Props` from `./types`\n- Use named default function export\n- Import from `@ikas/bp-storefront` (not `@ikas/storefront`)\n- Use `<IkasComponentRenderer>` for COMPONENT_LIST props\n- Use `getDefaultSrc()` for images\n- Use scoped CSS classes (not Tailwind)\n\n### 4. Barrel Export\n\n`src/components/index.ts`:\n```typescript\nexport { default as HeaderSection } from './HeaderSection';\nexport { default as HeroBanner } from './HeroBanner';\nexport { default as SlideItem } from './SlideItem';\n// ... etc\n```\n\n## Naming Conventions\n\n| Old | New |\n|----|-----|\n| Component name: `Navbar` | Section: `HeaderSection`, ID: `my-theme-header-section` |\n| Custom type: `Maaza` | Child component: `StoreCard`, ID: `my-theme-store-card` |\n| Custom type: `GrselveLink` | Child component: `BannerSlide`, ID: `my-theme-banner-slide` |\n\nUse descriptive English names even if the old theme used Turkish names.\n\n## Common Pitfalls\n\n1. **Don't forget `filteredComponentIds`** — Without it, any component can be placed in a COMPONENT_LIST slot\n2. **Don't manually write types.ts** — Always let the CLI generate it\n3. **Don't use `observer()` on root components** — Only on sub-components\n4. **Convert all `IkasSlider` usage** — `.value` access must become direct number access\n5. **Replace ALL library imports** — No swiper, headlessui, recharts, etc.\n6. **Convert Tailwind to plain CSS** — No utility classes in the new system",
56
- "tags": ["migration", "project", "generation", "ikas-config", "global-css", "structure", "complete"]
138
+ "tags": [
139
+ "migration",
140
+ "project",
141
+ "generation",
142
+ "ikas-config",
143
+ "global-css",
144
+ "structure",
145
+ "complete"
146
+ ]
57
147
  },
58
148
  "settings-conversion": {
59
149
  "title": "Theme Settings Conversion",
60
150
  "description": "How to convert old theme settings (colors, fonts, favicon) to the new system",
61
151
  "content": "## Colors\n\nOld theme settings define CSS custom properties:\n```json\n\"settings\": {\n \"colors\": [\n { \"displayName\": \"Primary\", \"color\": \"#fed9d9\", \"key\": \"--primary\" },\n { \"displayName\": \"Button BG\", \"color\": \"#000000\", \"key\": \"--button-bg-1\" }\n ]\n}\n```\n\nConvert to CSS variables in `global.css`:\n```css\n:root {\n --primary: #fed9d9;\n --button-bg-1: #000000;\n}\n```\n\nThese variables can then be used in component CSS files:\n```css\n.button { background-color: var(--button-bg-1); }\n```\n\nAlternatively, expose colors as COLOR props on individual components so store owners can customize per-component.\n\n## Fonts\n\nOld:\n```json\n\"fontFamily\": { \"name\": \"Quicksand\", \"variants\": [\"300\", \"regular\", \"500\", \"600\", \"700\"] }\n```\n\nNew `global.css`:\n```css\n@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap');\n\nbody {\n font-family: 'Quicksand', sans-serif;\n}\n```\n\n## Favicon\n\nFavicon handling is outside the code-component scope. It's managed at the store level.\n\n## Stock Preference\n\nThe `stockPreference` setting (SHOW_ALL, HIDE_OUT_OF_STOCK) is handled by the storefront API, not component code.\n\n## See Also\n- `get_framework_guide(\"global-css\")` — full details on global.css and CSS variables\n- `get_framework_guide(\"css-scoping\")` — how component CSS is scoped vs global",
62
- "tags": ["migration", "settings", "colors", "fonts", "css-variables", "global-css"]
152
+ "tags": [
153
+ "migration",
154
+ "settings",
155
+ "colors",
156
+ "fonts",
157
+ "css-variables",
158
+ "global-css"
159
+ ]
63
160
  },
64
161
  "finding-new-system-equivalents": {
65
162
  "title": "Finding New-System Equivalents for Old APIs",
66
163
  "description": "How to discover the new-system replacement for any old imperative API, store method, or feature. Use this instead of guessing or assuming old APIs still exist.",
67
164
  "content": "The new system (`@ikas/bp-storefront`) is a **completely separate implementation** from the old system (`@ikas/storefront`). Most old imperative APIs have been replaced with form-model patterns, direct store imports, or dedicated functions. **Never assume an old API name works in the new system.** Look it up first.\n\n## The Discovery Protocol\n\nWhen you encounter any old-system API (e.g., `customerStore.createEmailSubscription`, `useStore()`, `<Image>`, `toast.success`, `Router.push`), follow this protocol:\n\n### Step 1: Extract the intent\n\nStrip the old API down to what it *does*, not how it's called. Examples:\n- `customerStore.createEmailSubscription(email)` → intent: \"subscribe email to newsletter\"\n- `await customerStore.login({ email, password })` → intent: \"customer login\"\n- `searchStore.search(query)` → intent: \"product search\"\n- `cartStore.addProduct(variantId, qty)` → intent: \"add item to cart\"\n- `Router.push('/category/foo')` → intent: \"navigate to a page\"\n\n### Step 2: Search the new system\n\nUse one or more of these tools, in order of specificity:\n\n1. **`search_docs(\"<intent keywords>\")`** — searches across functions, framework topics, types, and migration guides. Always try this first.\n ```\n search_docs(\"newsletter email subscription\")\n search_docs(\"customer login\")\n search_docs(\"add to cart\")\n search_docs(\"navigate page\")\n ```\n\n2. **`list_functions(\"<category>\")`** — lists all functions in a category. Useful when you know the domain:\n ```\n list_functions(\"Form\") # newsletter, login, register, address forms\n list_functions(\"Cart\") # add/remove/update cart items\n list_functions(\"Customer\") # login, logout, register, profile\n list_functions(\"Navigation\") # Router.navigate and related\n list_functions(\"Validation\") # form validation helpers\n ```\n\n3. **`search_types(\"<keyword>\")`** — when you need a data shape:\n ```\n search_types(\"newsletter\")\n search_types(\"login form\")\n ```\n\n4. **`get_model_guide(\"<TypeName>\")`** — one-stop shop for a type: definition + all utility functions + examples + related types. Use once you've identified the relevant model.\n\n5. **`get_function_doc(\"<functionName>\")`** — exact signature, parameters, return type, example for a specific function.\n\n6. **`get_code_example(\"<task>\")`** — working code patterns for common tasks. Examples: `\"add to cart\"`, `\"variant selection\"`, `\"navigation\"`, `\"image-handling\"`, or specific sections like `\"login-section\"`, `\"cart-section\"`.\n\n7. **`get_framework_guide(\"form-handling\")`** / **`get_framework_guide(\"async-data-patterns\")`** — broader patterns that apply across many features.\n\n### Step 3: Recognize the common new-system patterns\n\nMany old imperative calls map to one of these new-system shapes:\n\n- **Forms (login, register, newsletter, contact, address, review submission):** look for a `get<Feature>Form()` + `submit<Feature>Form()` pair, usually with `set<Feature>Form<Field>()` setters. This is the universal form-model pattern.\n- **Cart / favorites / customer actions:** top-level functions exported directly from `@ikas/bp-storefront` (e.g. `addItemToCart`, `addToFavorites`) that mutate store state.\n- **Navigation:** `Router.navigate(href)` or `Router.navigateToPage(pageType, params)`.\n- **Page data (current product, category, blog):** imported stores read as observables (e.g. `productStore.product`, `categoryStore.category`).\n\n### Step 4: If you can't find it\n\nIf `search_docs` and `list_functions` return nothing relevant, the feature may:\n- Not exist in the new system (drop it, or use a TEXT prop for static content)\n- Be handled outside components (analytics, PDF export, etc.)\n- Require a creative workaround (e.g. a `date-fns` helper → inline `Intl.DateTimeFormat`)\n\nDocument what you couldn't find in the **Session Log** of MIGRATION.md so future sessions don't repeat the search.\n\n## Example: Migrating an Unfamiliar Old API\n\nSuppose the old Footer code does:\n```tsx\nimport { customerStore } from \"@ikas/storefront\";\nawait customerStore.createEmailSubscription(email);\ntoast.success(\"Subscribed!\");\n```\n\nDiscovery steps:\n1. `search_docs(\"email subscription newsletter\")` → finds `getNewsletterSubscriptionForm`, `submitNewsletterSubscriptionForm`, etc.\n2. `get_function_doc(\"submitNewsletterSubscriptionForm\")` → exact signature + example\n3. `get_framework_guide(\"form-handling\")` → the form-model pattern to apply\n4. For the `toast.success` call: this is a library replacement, not an ikas API. See `get_migration_guide(\"library-replacements\")` — it maps to inline status text with TEXT props.\n\n## Rule of Thumb\n\n**If the old code imports from `@ikas/storefront`, search before you write.** The function name is almost never the same in `@ikas/bp-storefront`, and many old methods have been replaced by entirely different patterns.\n\n## See Also\n- `get_migration_guide(\"storefront-import-mapping\")` — high-level type/function rename table\n- `get_migration_guide(\"react-to-preact\")` — imports, hooks, observer rules\n- `get_framework_guide(\"imports\")` — where each new-system function lives\n- `get_framework_guide(\"form-handling\")` — the form-model pattern all forms use",
68
- "tags": ["migration", "discovery", "search", "find", "equivalent", "replacement", "api", "imperative", "useStore", "newsletter", "login", "register", "form-model", "search_docs", "list_functions"]
165
+ "tags": [
166
+ "migration",
167
+ "discovery",
168
+ "search",
169
+ "find",
170
+ "equivalent",
171
+ "replacement",
172
+ "api",
173
+ "imperative",
174
+ "useStore",
175
+ "newsletter",
176
+ "login",
177
+ "register",
178
+ "form-model",
179
+ "search_docs",
180
+ "list_functions"
181
+ ]
69
182
  },
70
183
  "iterative-workflow": {
71
184
  "title": "Iterative Migration Workflow (Resumable, Section-by-Section)",
72
185
  "description": "The recommended four-phase workflow for migrating large themes across multiple sessions. Use this for any theme with more than ~5 sections.",
73
- "content": "## Why Iterative?\n\nA full theme migration (30-50 sections, 20-40 customData types, hundreds of props) is too large to complete in a single LLM session. Without a persistent plan, each session re-derives the same decisions (CSS variables, shared sub-components, component IDs), wastes context, and produces inconsistent output.\n\n**The solution:** A `MIGRATION.md` file at the new project root that serves as the source of truth across sessions. All sessions read it first and update it as they go.\n\n## The Four Phases\n\n### Phase A: Plan (run once)\n\nCall `plan_migration(theme_json, old_source_dir)` to generate a complete migration plan. The LLM writes the returned markdown to `<new-project-root>/MIGRATION.md`.\n\nThis produces:\n- All CSS variables to add to `src/global.css`\n- Font setup\n- Custom enums to create (with pre-computed CLI commands)\n- Shared sub-components detected from old source (with usage list)\n- Section queue grouped by complexity (simple / medium / complex)\n- Canonical component IDs for every section + child\n- A status legend (`[ ]` / `[~]` / `[x]`)\n\n### Phase B: Foundation (run once)\n\nExecute the checklist at the top of MIGRATION.md, in order:\n1. Create `src/global.css` with extracted CSS variables + font imports\n2. Run the `config add-enum` CLI commands listed under Custom Enums\n3. Create each shared sub-component in `src/sub-components/<Name>/` (index.tsx + styles.css — NOT registered in ikas.config.json)\n4. Update each checkbox in MIGRATION.md from `[ ]` to `[x]` as you go\n\n### Phase C: Section Migration (loop)\n\nFor each section in the queue (start with Simple, then Medium, then Complex):\n1. Call `get_section_migration_plan(theme_json, section_name)` returns concrete ikas.config.json entries, CLI commands, source file paths, and references to relevant framework/migration guides\n2. Read the listed old source files\n3. Run the CLI commands (creates component + props + types.ts)\n4. Write `index.tsx` and `styles.css` using the plan's references (do NOT manually write types.ts)\n5. Mark the section `[x]` in MIGRATION.md\n\n### Phase D: Resume (any session)\n\nAt the start of any new session:\n1. Check if `MIGRATION.md` exists in the project root. If yes, **read it first before doing anything else**.\n2. Find the first unchecked `[ ]` item. That's where you resume.\n3. Do NOT re-derive anything already in MIGRATION.md (CSS variables, component IDs, sub-components). They're the committed plan.\n4. If a sub-component is listed as `[x]` complete, import it from `src/sub-components/<Name>`. Do not re-create it.\n\n## Critical Rules\n\n- **MIGRATION.md is the authority.** Component IDs, CSS variable names, and sub-component names listed there are final. Do not change them mid-migration.\n- **Update checkboxes immediately.** Mark `[~]` when you start, `[x]` when the section builds cleanly. This is how the next session knows where to resume.\n- **Shared sub-components come BEFORE sections.** If a section references a shared sub-component that isn't yet `[x]`, build the sub-component first.\n- **Do not invent new shared sub-components mid-migration.** If you discover one, add it to MIGRATION.md's Shared Sub-Components list first (with a `[ ]` checkbox), then build it.\n- **For simple migrations (< 5 sections)** the iterative workflow is overkill — use `analyze_old_theme` + direct implementation.\n\n## See Also\n- `get_migration_guide(\"migration-overview\")` — big-picture system differences\n- `get_migration_guide(\"component-renderer-limitations\")` — critical constraints when using COMPONENT_LIST\n- `get_migration_guide(\"prop-runtime-shapes\")` — what each prop type actually receives at runtime",
74
- "tags": ["migration", "workflow", "iterative", "resumable", "MIGRATION.md", "plan", "session", "continue"]
186
+ "content": "## Why Iterative?\n\nA full theme migration (30-50 sections, dozens of customData types, hundreds of props) is too large to complete in a single LLM session. Without a persistent plan, each session re-derives the same decisions, wastes context, and produces inconsistent output.\n\n**The solution:** A `MIGRATION.md` file at the new project root that the MCP **writes once as an initial scaffold**. From that point on, the LLM owns the file — ticks checkboxes, logs custom-data decisions, and corrects anything the MCP couldn't know.\n\n## MCP vs LLM Responsibilities\n\nThe MCP cannot do everything for you:\n\n- **MCP writes:** The initial MIGRATION.md (once, via `plan_migration` with `project_root`). After that the MCP never modifies the file.\n- **MCP provides:** Reference content via read-only tools (`get_section_migration_plan`, `get_migration_guide`, `get_section_template`, etc.).\n- **LLM owns:** All ongoing updates to MIGRATION.md — checkboxes, decisions log, source-code-analysis findings, notes.\n- **LLM must do:** Source code analysis. theme.json is incomplete; atomic components (Button, Input, Card, icons) often live only in `src/` and the MCP cannot see them. You must scan the old source.\n- **LLM decides:** Every customData enum-vs-component choice (see `get_migration_guide(\"custom-data-conversion\")`).\n\n## The Four Phases\n\n### Phase A: Plan + Source Analysis (run once)\n\n1. Call `plan_migration({theme_json_path, project_root, project_name, old_source_dir})`. Pass `theme_json_path` (absolute) — real-world theme.json files are too large to pass as a raw string. Pass `project_root` so the MCP writes MIGRATION.md directly.\n2. **Read the generated MIGRATION.md start-to-finish.** Pay attention to the \"READ THIS FIRST\" preamble.\n3. **Do source code analysis.** Scan the old project's `src/` for atomic components NOT referenced from theme.json Button, Input, Card, icon wrappers, layout helpers, formatters. theme.json only lists theme-level components (sections); atomic primitives are invisible to the MCP. Add what you find under `## Source Code Analysis` in MIGRATION.md. Promote anything that's used by multiple sections to `### Shared Sub-Components` in the Foundation block.\n\n### Phase B: Foundation (run once)\n\nExecute the Foundation checklist in MIGRATION.md, in order:\n1. Create `src/global.css` with extracted CSS variables + font imports. Tick each `[ ]` to `[x]` as you complete it.\n2. Create each shared sub-component in `src/sub-components/<Name>/` (index.tsx + styles.css — NOT registered in ikas.config.json).\n3. **Note:** Custom enums are NOT pre-created. They are decided per-section in Phase C.\n\n### Phase C: Section Migration (loop)\n\nFor each section in the queue (start Simple, then Medium, then Complex):\n\n1. Call `get_section_migration_plan({theme_json_path, section_name, project_name})`. Returns: concrete CLI commands, prop-by-prop conversion table, and a **\"Custom Data Decisions to Make\"** callout listing every prop that references a customData type.\n2. Read the listed old source files.\n3. **Make customData decisions** for any callouts. For each: pick enum or component, run the corresponding CLI command, and **log the decision in MIGRATION.md under `## Custom Data Decisions`** with reasoning.\n4. Run the CLI commands (creates parent + children with auto-generated types.ts).\n5. Write `index.tsx` and `styles.css`.\n6. Mark the section `[x]` in MIGRATION.md (use your file-editing tool — there is no MCP tool for this).\n\n### Phase D: Resume (any session)\n\nThere is **no `resume_migration` tool**. The MCP is fully stateless after the initial `plan_migration` write.\n\n1. Read `<project_root>/MIGRATION.md` in full. The \"READ THIS FIRST\" preamble explains your responsibilities.\n2. Find the first unchecked `[ ]` in the Sections queue. That's where you resume.\n3. Check whether `## Source Code Analysis` is populated. If empty, the previous session didn't complete Phase A — finish it first.\n4. Check `## Custom Data Decisions` to see which customData types have already been resolved. Do NOT re-decide them.\n\n## Critical Rules\n\n- **MIGRATION.md is authoritative.** Component IDs, CSS variable names, and sub-component names listed there are final. Do not change them mid-migration.\n- **Update checkboxes immediately** as you finish work. This is how the next session knows where to resume.\n- **Log every customData decision.** Use the format documented in the file's commented example. Skipping this causes future sessions to re-decide and produce inconsistent component vs enum choices.\n- **Source code analysis is your job.** The MCP cannot find Button/Input/Card primitives — you must scan src/.\n- **Shared sub-components come BEFORE the sections that use them.** If a section depends on a sub-component that isn't yet `[x]`, build the sub-component first.\n- **For small themes (< 5 sections)** the iterative workflow is overkill — use direct implementation with `analyze_old_theme`.\n\n## See Also\n- `get_migration_guide(\"migration-overview\")` — big-picture system differences and responsibility split\n- `get_migration_guide(\"custom-data-conversion\")` — enum-vs-component decision framework with worked examples\n- `get_migration_guide(\"component-renderer-limitations\")` — critical constraints when using COMPONENT_LIST\n- `get_migration_guide(\"prop-runtime-shapes\")` — what each prop type actually receives at runtime\n",
187
+ "tags": [
188
+ "migration",
189
+ "workflow",
190
+ "iterative",
191
+ "resumable",
192
+ "MIGRATION.md",
193
+ "plan",
194
+ "session",
195
+ "continue"
196
+ ]
75
197
  },
76
198
  "component-renderer-limitations": {
77
199
  "title": "IkasComponentRenderer: Critical Limitations",
78
200
  "description": "Essential constraints to understand BEFORE designing sections with COMPONENT_LIST props. These limitations fundamentally shape migration decisions.",
79
201
  "content": "`IkasComponentRenderer` is the mechanism that renders child components placed in `COMPONENT` and `COMPONENT_LIST` slots. It has important architectural constraints that affect how you migrate old CUSTOM/DYNAMIC_LIST props.\n\n## Constraint 1: Parent Cannot Read Child Prop Values\n\n**This is the single most important constraint.** When a store owner places child components in a `COMPONENT_LIST` slot, the parent component receives them as opaque `any[]` values. The parent cannot inspect their individual prop values.\n\n```tsx\nexport default function MySection({ items, ...props }: Props) {\n // ❌ CANNOT do this — items[0].title is NOT accessible:\n // const firstTitle = items[0].title;\n\n // ✅ CAN only do this — hand the list to IkasComponentRenderer:\n return (\n <section>\n <IkasComponentRenderer id=\"items\" components={items as any[]} parentProps={props} />\n </section>\n );\n}\n```\n\n### Migration Implication\n\nWhen an old component had logic that depended on child data (e.g., \"if any menu item has an icon, render the icon column\"), that logic must move **into** the child component — not stay in the parent.\n\n## Constraint 2: Child Components Access Parent Data Via `parentProps`\n\nChildren don't automatically see the parent's props. The parent must pass `parentProps={props}` to `IkasComponentRenderer`, and children access parent data via `privateVarMap` mapping or by receiving specific props declared in the parent's COMPONENT_LIST config.\n\n**For detailed patterns, use `get_framework_guide(\"private-var-map\")` and `get_framework_guide(\"component-renderer-patterns\")`.**\n\n## Constraint 3: `id` Must Be Unique Per Renderer Instance\n\nEach `<IkasComponentRenderer>` in your JSX must have a unique `id`, even when rendering the same `components` array. This matters for desktop/mobile dual renders:\n\n```tsx\n// Desktop menu\n<IkasComponentRenderer id=\"menu-desktop\" components={menuLinks as any[]} parentProps={props} />\n\n// Same list, mobile menu — different id\n<IkasComponentRenderer id=\"menu-mobile\" components={menuLinks as any[]} parentProps={props} />\n```\n\nReusing the same `id` in two renderer instances causes undefined behavior.\n\n## Decision Tree: What Replaces Old CUSTOM/DYNAMIC_LIST\n\nGiven the constraints above, here's how to decide:\n\n| Old data shape | New approach |\n|----------------|--------------|\n| List of simple links | `LIST_OF_LINK` prop — parent has full data access |\n| List of links with icon/color/label only | Depends: if parent needs to read them → use multiple flat props; if purely rendered → `COMPONENT_LIST` with a LinkItem child |\n| List of rich items (image + text + button + settings) | `COMPONENT_LIST` + child component (standard pattern) |\n| List where parent logic depends on item contents | **Rethink.** Either move the logic into the child, or flatten to multiple parent props |\n| Fixed-count structured data (e.g., 3-feature grid with title + icon each) | Either flatten into parent props (feature1Title, feature1Icon, ...) OR `COMPONENT_LIST` — choose based on whether order/count should be editor-controlled |\n| Deeply nested data (menu with sub-menu with images) | Nested `COMPONENT_LIST` across multiple child component levels |\n\n## Constraint 4: No Indexed Access to Children\n\nThe parent receives `COMPONENT_LIST` as an opaque array. You **cannot** access specific children by index for positional rendering:\n\n```tsx\n// ❌ CANNOT do this — old pattern that accessed children by position:\n// const firstChild = components[0];\n// const restChildren = components.slice(1);\n\n// ✅ CAN only render the whole list at once:\n<IkasComponentRenderer id=\"items\" components={items as any[]} parentProps={props} />\n```\n\n### Migration Implication\n\nOld components sometimes used indexed access for layout purposes (e.g., a bento grid where `components[0]` spans 2 rows and `components[1:]` fill the rest). In the new system, use **CSS** to achieve positional layouts:\n\n```css\n/* CSS :nth-child selectors for positional layout */\n.bento-grid > :first-child { grid-row: span 2; }\n```\n\nAlternatively, if specific positions need truly different component types, use **multiple COMPONENT_LIST props** (e.g., `mainItem` as `COMPONENT` + `sideItems` as `COMPONENT_LIST`) instead of one list with indexed access.\n\n## `filteredComponentIds` — Restricting Allowed Children\n\nAlways set `filteredComponentIds` on `COMPONENT_LIST` props to restrict which children can be placed there. Without it, the editor allows any registered component to be placed in the slot.\n\n```json\n{\n \"name\": \"menuItems\",\n \"type\": \"COMPONENT_LIST\",\n \"filteredComponentIds\": [\"my-theme-menu-link\"]\n}\n```\n\n## See Also\n- `get_framework_guide(\"component-renderer-patterns\")` — full usage patterns and examples\n- `get_framework_guide(\"private-var-map\")` — how to push parent data to children\n- `get_framework_guide(\"config-advanced-features\")` — filteredComponentIds, isHeader, etc.\n- `get_migration_guide(\"custom-data-conversion\")` — step-by-step DYNAMIC_LIST → COMPONENT_LIST migration",
80
- "tags": ["migration", "IkasComponentRenderer", "COMPONENT_LIST", "limitations", "parent-child", "privateVarMap", "filteredComponentIds"]
202
+ "tags": [
203
+ "migration",
204
+ "IkasComponentRenderer",
205
+ "COMPONENT_LIST",
206
+ "limitations",
207
+ "parent-child",
208
+ "privateVarMap",
209
+ "filteredComponentIds"
210
+ ]
81
211
  },
82
212
  "prop-runtime-shapes": {
83
213
  "title": "Runtime Shapes of Prop Types",
84
214
  "description": "Exact TypeScript shapes your component receives at runtime for each prop type. Covers common confusions (.data vs .links, .variant vs .product).",
85
- "content": "When you declare a prop in `ikas.config.json`, the component receives a specific runtime shape. This table documents what you actually get — which is often different from the editor UI suggests and from naive expectations.\n\n## Reference Table\n\n| PropType | Runtime Shape | Key Access Pattern |\n|----------|---------------|--------------------|\n| `TEXT` | `string` | `props.myText` |\n| `RICH_TEXT` | `string` (HTML) | `<div dangerouslySetInnerHTML={{ __html: props.html }} />` |\n| `NUMBER` | `number` | `props.width` (direct number, NOT `.value`) |\n| `BOOLEAN` | `boolean` | `props.enabled` |\n| `COLOR` | `string` (CSS color) | `style={{ color: props.textColor }}` |\n| `IMAGE` | `IkasImage \\| null` | `getDefaultSrc(props.image)` or `getSrc(props.image, { width: 400 })` |\n| `IMAGE_LIST` | `IkasImageList` | `props.images.map(img => getDefaultSrc(img))` |\n| `VIDEO` | `IkasVideo \\| null` | Check `.url`, `.type` (EMBED vs FILE) |\n| `LINK` | `IkasNavigationLink` | `props.link.href`, `props.link.label`, `props.link.subLinks`, `props.link.openInNewTab` |\n| `LIST_OF_LINK` | `IkasNavigationLinkList` | **`props.links.links`** — access via `.links` property (NOT `.data`) |\n| `PRODUCT` | `IkasProduct \\| null` | `props.product.name`, `.prices`, etc. |\n| `PRODUCT_LIST` | `IkasProductList` | **`props.productList.data`** — array of products via `.data` property |\n| `CATEGORY` | `IkasCategory \\| null` | `props.category.name`, `.href` |\n| `CATEGORY_LIST` | `IkasCategoryList` | Check shape with `get_type_definition(\"IkasCategoryList\")` |\n| `BRAND` / `BRAND_LIST` | `IkasBrand \\| null` / `IkasBrandList` | Use `get_model_guide(\"IkasBrand\")` |\n| `BLOG` / `BLOG_LIST` | `IkasBlog \\| null` / `IkasBlogList` | Blog list uses `.data` |\n| `PRODUCT_ATTRIBUTE` | `IkasProductAttributeValue \\| null` | Direct single value |\n| `PRODUCT_ATTRIBUTE_LIST` | `IkasProductAttributeValue[]` | Plain array (NOT wrapped) |\n| `ENUM` | `string` (the enum value) | Compare directly: `props.type === \"email\"` |\n| `COMPONENT` | `any` | Pass to `<IkasComponentRenderer components={[comp] as any[]} />` |\n| `COMPONENT_LIST` | `any[]` | Pass to `<IkasComponentRenderer components={list as any[]} />` |\n\n## Critical: Inconsistent Access Patterns\n\nThe most common mistakes come from **list-type props having inconsistent access patterns**:\n\n- `PRODUCT_LIST` → `.data` (array of products)\n- `BLOG_LIST` → `.data` (array of blogs)\n- `LIST_OF_LINK` → `.links` (NOT `.data`)\n- `PRODUCT_ATTRIBUTE_LIST` → plain array (no wrapper)\n- `IMAGE_LIST` → plain array via `.data` (check with `get_type_definition`)\n\n**When in doubt, call `get_model_guide(\"IkasProductList\")` etc. for the exact shape.**\n\n## Image Sizing: getDefaultSrc vs getSrc\n\n- `getDefaultSrc(image)` — returns the default-sized URL. Use for small images, thumbnails, or when size doesn't matter.\n- `getSrc(image, { width: 400 })` — returns a URL at a specific width. Use for performance-critical images where you know the rendered size.\n- `createMediaSrcset(image)` — generates a responsive `srcset` string for `<img srcset={...}>`.\n\n**Rule of thumb:** Use `getSrc` with a concrete width for large images; use `getDefaultSrc` for icons/small images.\n\n## Store Data Shapes (cartStore, customerStore, etc.)\n\nStore data is NOT a prop type — you import stores directly from `@ikas/bp-storefront`. Their runtime shapes are:\n\n- `cartStore.cart` → `IkasCart`: `.orderLineItems` is the array; each item has **`.variant`** (not `.product`), `.quantity`, `.totalPrice`\n- `customerStore.customer` → `IkasCustomer | null`: null when logged out, has `.firstName`, `.lastName`, `.email`, `.id`\n- `customerStore.isAuthenticated` → boolean\n\n**For full store shapes, always use `get_model_guide(\"IkasCart\")`, `get_model_guide(\"IkasCustomer\")`, etc. — do not guess.**\n\n## Old → New Runtime Shape Changes\n\nWhen migrating, remember these runtime changes:\n\n- Old `IkasSlider` (with `.value`) → New `number` (plain). Replace `width?.value` with just `width`.\n- Old `<Image>` component (renders itself) → New `getDefaultSrc(image)` + native `<img>`.\n- Old `useStore()` hook → New direct import: `import { cartStore } from '@ikas/bp-storefront'`.\n- Old `IkasAttributeDetail` / `IkasAttributeList` → New `IkasProductAttributeValue` / array.\n\n## See Also\n- `get_model_guide(\"<TypeName>\")` — one-stop shop for type definition + utility functions + examples\n- `get_type_definition(\"<TypeName>\")` — raw type definition\n- `get_framework_guide(\"common-pitfalls\")` — runtime shape mistakes (point #9 covers `.data`)\n- `get_framework_guide(\"imports\")` — where each type/function comes from",
86
- "tags": ["migration", "runtime", "shapes", "PRODUCT_LIST", "LIST_OF_LINK", "IkasCart", "getDefaultSrc", "getSrc", "data", "links", "variant"]
215
+ "content": "When you declare a prop in `ikas.config.json`, the component receives a specific runtime shape. This table documents what you actually get — which is often different from the editor UI suggests and from naive expectations.\n\n## Reference Table\n\n| PropType | Runtime Shape | Key Access Pattern |\n|----------|---------------|--------------------|\n| `TEXT` | `string` | `props.myText` |\n| `RICH_TEXT` | `string` (HTML) | `<div dangerouslySetInnerHTML={{ __html: props.html }} />` |\n| `NUMBER` | `number` | `props.width` (direct number, NOT `.value`) |\n| `BOOLEAN` | `boolean` | `props.enabled` |\n| `COLOR` | `string` (CSS color) | `style={{ color: props.textColor }}` |\n| `IMAGE` | `IkasImage \\| null` | `getDefaultSrc(props.image)` or `getSrc(props.image, { width: 400 })` |\n| `IMAGE_LIST` | `IkasImageList` | `props.images.map(img => getDefaultSrc(img))` |\n| `VIDEO` | `IkasVideo \\| null` | Check `.url`, `.type` (EMBED vs FILE) |\n| `LINK` | `IkasNavigationLink` | `props.link.href`, `props.link.label`, `props.link.subLinks`, `props.link.openInNewTab` |\n| `LIST_OF_LINK` | `IkasNavigationLinkList` | **`props.links.links`** — access via `.links` property (NOT `.data`) |\n| `PRODUCT` | `IkasProduct \\| null` | `props.product.name`, `.prices`, etc. |\n| `PRODUCT_LIST` | `IkasProductList` | **`props.productList.data`** — array of products via `.data` property |\n| `CATEGORY` | `IkasCategory \\| null` | `props.category.name`, `.href` |\n| `CATEGORY_LIST` | `IkasCategoryList` | Check shape with `get_type_definition(\"IkasCategoryList\")` |\n| `BRAND` / `BRAND_LIST` | `IkasBrand \\| null` / `IkasBrandList` | Use `get_model_guide(\"IkasBrand\")` |\n| `BLOG` / `BLOG_LIST` | `IkasBlog \\| null` / `IkasBlogList` | Blog list uses `.data` |\n| `PRODUCT_ATTRIBUTE` | `IkasProductAttributeValue \\| null` | Direct single value |\n| `PRODUCT_ATTRIBUTE_LIST` | `IkasProductAttributeValue[]` | Plain array (NOT wrapped) |\n| `ENUM` | `string` (the enum value) | Compare directly: `props.type === \"email\"` |\n| `COMPONENT` | `any` | Pass to `<IkasComponentRenderer components={[comp] as any[]} />` |\n| `COMPONENT_LIST` | `any[]` | Pass to `<IkasComponentRenderer components={list as any[]} />` |\n\n## Critical: Inconsistent Access Patterns\n\nThe most common mistakes come from **list-type props having inconsistent access patterns**:\n\n- `PRODUCT_LIST` → `.data` (array of products)\n- `BLOG_LIST` → `.data` (array of blogs)\n- `LIST_OF_LINK` → `.links` (NOT `.data`)\n- `PRODUCT_ATTRIBUTE_LIST` → plain array (no wrapper)\n- `IMAGE_LIST` → plain array via `.data` (check with `get_type_definition`)\n\n**When in doubt, call `get_model_guide(\"IkasProductList\")` etc. for the exact shape.**\n\n## Image Sizing: getDefaultSrc vs getSrc\n\n- `getDefaultSrc(image)` — returns the default-sized URL. Use for small images, thumbnails, or when size doesn't matter.\n- `getSrc(image, { width: 400 })` — returns a URL at a specific width. Use for performance-critical images where you know the rendered size.\n- `createMediaSrcset(image)` — generates a responsive `srcset` string for `<img srcset={...}>`.\n\n**Rule of thumb:** Use `getSrc` with a concrete width for large images; use `getDefaultSrc` for icons/small images.\n\n## Store Data Shapes (cartStore, customerStore, etc.)\n\nStore data is NOT a prop type — you import stores directly from `@ikas/bp-storefront`. Their runtime shapes are:\n\n- `cartStore.cart` → `IkasCart`: `.orderLineItems` is the array; each item has **`.variant`** (not `.product`), `.quantity`, `.totalPrice`\n- `customerStore.customer` → `IkasCustomer | null`: null when logged out, has `.firstName`, `.lastName`, `.email`, `.id`\n- `customerStore.isAuthenticated` → boolean\n\n**For full store shapes, always use `get_model_guide(\"IkasCart\")`, `get_model_guide(\"IkasCustomer\")`, etc. — do not guess.**\n\n## Old → New Runtime Shape Changes\n\nWhen migrating, remember these runtime changes:\n\n- Old `IkasSlider` (with `.value`) → New `number` (plain). Replace `width?.value` with just `width`.\n- Old `<Image>` component (renders itself) → New `getDefaultSrc(image)` + native `<img>`.\n- Old `useStore()` hook → New direct import: `import { cartStore } from '@ikas/bp-storefront'`.\n- Old `IkasAttributeDetail` / `IkasAttributeList` → New `IkasProductAttributeValue` / array.\n\n\n\n## Order Line Item Shapes (cart, order pages)\n\nThese come up when iterating over `IkasCart.orderLineItems` or `IkasOrder.orderLineItems`. They trip up LLMs because the field names don't match scalar intuition:\n\n- **`IkasOrderLineItemOption.values`** — this is an **array** of `IkasOrderLineItemOptionValue`, NOT a scalar `.value`. Iterate it with `.map`:\n\n ```tsx\n // WRONG — there is no .value on IkasOrderLineItemOption\n <span>{option.value}</span>\n\n // RIGHT — .values is an array, each item has its own .name/.value/.formattedPrice\n {option.values.map((v) => (\n <span key={v.name}>{v.name}: {v.value}</span>\n ))}\n ```\n\n- **`IkasOrderAdjustment` has no `.formattedAmount` property.** Call the helper instead: `getOrderAdjustmentFormattedAmount(adjustment)` from `@ikas/bp-storefront`. The helper returns the formatted currency string and handles the sign (negated for decrements). See `get_function_doc(\"getOrderAdjustmentFormattedAmount\")`.\n\n## See Also\n- `get_model_guide(\"<TypeName>\")` — one-stop shop for type definition + utility functions + examples\n- `get_type_definition(\"<TypeName>\")` — raw type definition\n- `get_framework_guide(\"common-pitfalls\")` — runtime shape mistakes (point #9 covers `.data`)\n- `get_framework_guide(\"imports\")` — where each type/function comes from",
216
+ "tags": [
217
+ "migration",
218
+ "runtime",
219
+ "shapes",
220
+ "PRODUCT_LIST",
221
+ "LIST_OF_LINK",
222
+ "IkasCart",
223
+ "getDefaultSrc",
224
+ "getSrc",
225
+ "data",
226
+ "links",
227
+ "variant"
228
+ ]
87
229
  },
88
230
  "link-prop-decision-guide": {
89
231
  "title": "LINK Prop Decision Guide",
90
232
  "description": "When to use LINK, LIST_OF_LINK, LINK with subLinks, or COMPONENT_LIST for link-related data. Addresses common confusion between similar prop types.",
91
233
  "content": "Several prop types can represent \"links\" in the new system. Choosing the right one depends on the shape of the data and what the store owner should control.\n\n## Decision Tree\n\n**Is it a single link?**\n→ Use `LINK`. Runtime: `IkasNavigationLink { href, label, subLinks, openInNewTab, ... }`\n\n**Is it a flat list of sibling links (no extra data per link)?**\n→ Use `LIST_OF_LINK`. Runtime: `IkasNavigationLinkList` with `.links: IkasNavigationLink[]`\n\n**Is it a hierarchical menu (links with sub-links, no extra data)?**\n→ Use `LIST_OF_LINK`. Each `IkasNavigationLink` already has `.subLinks: IkasNavigationLink[]`. The editor handles sub-link nesting natively.\n\n**Is it a list of links WITH extra per-link data (image, color, badge, mega-menu content)?**\n→ Use `COMPONENT_LIST` with a child component that has one `LINK` prop + the extra fields.\n\n**Is it a list of products/categories/blogs (not generic links)?**\n→ Use the specific prop type (`PRODUCT_LIST`, `CATEGORY_LIST`, `BLOG_LIST`), NOT `LIST_OF_LINK`.\n\n## Examples\n\n### Footer bottom bar: \"Privacy | Terms | Contact\"\nSimple flat list → `LIST_OF_LINK`:\n```json\n{ \"name\": \"bottomLinks\", \"type\": \"LIST_OF_LINK\" }\n```\nAccess: `props.bottomLinks.links.map(link => <a href={link.href}>{link.label}</a>)`\n\n### Main nav menu with dropdowns: \"Women (→ Tops, Bottoms, Shoes)\"\nHierarchical but just labels + links → `LIST_OF_LINK` (sub-links built-in):\n```json\n{ \"name\": \"menu\", \"type\": \"LIST_OF_LINK\" }\n```\nAccess: `props.menu.links.map(link => (<div>{link.label}{link.subLinks?.map(...)}</div>))`\n\n### Mega menu with images per column\nLinks PLUS image + custom layout per menu item → `COMPONENT_LIST`:\n```json\n// Parent:\n{ \"name\": \"menuItems\", \"type\": \"COMPONENT_LIST\", \"filteredComponentIds\": [\"my-theme-mega-menu-item\"] }\n// Child MegaMenuItem:\n{ \"props\": [\n { \"name\": \"mainLink\", \"type\": \"LINK\" },\n { \"name\": \"columnImage\", \"type\": \"IMAGE\" },\n { \"name\": \"subLinks\", \"type\": \"LIST_OF_LINK\" }\n]}\n```\n\n### Social icons: \"Instagram | Facebook | Twitter\" each with its own icon image\nList of links with an image each → `COMPONENT_LIST`:\n```json\n// Parent:\n{ \"name\": \"socials\", \"type\": \"COMPONENT_LIST\", \"filteredComponentIds\": [\"my-theme-social-link\"] }\n// Child SocialLink:\n{ \"props\": [\n { \"name\": \"link\", \"type\": \"LINK\" },\n { \"name\": \"icon\", \"type\": \"IMAGE\" }\n]}\n```\n\n## Old CUSTOM → Which Link Prop?\n\nWhen migrating an old CUSTOM/DYNAMIC_LIST:\n\n| Old customData shape | New prop |\n|----------------------|----------|\n| `OBJECT { link: LINK }` | `LINK` (flatten) |\n| `DYNAMIC_LIST of OBJECT { link: LINK }` | `LIST_OF_LINK` (if just label+href) or `COMPONENT_LIST` (if more fields) |\n| `DYNAMIC_LIST of OBJECT { link, image }` | `COMPONENT_LIST` + child component |\n| `DYNAMIC_LIST of OBJECT { mainlink, sublinks: LIST_OF_LINK }` | `LIST_OF_LINK` (subLinks are native) |\n| `DYNAMIC_LIST of OBJECT { mainlink, sublinks: DYNAMIC_LIST of OBJECT { links, images, cols } }` | `COMPONENT_LIST` + `MenuItem` child + nested `COMPONENT_LIST` for columns |\n\n## See Also\n- `get_migration_guide(\"prop-runtime-shapes\")` — exact shapes for each link type\n- `get_migration_guide(\"component-renderer-limitations\")` — COMPONENT_LIST constraints\n- `get_framework_guide(\"navigation-patterns\")` — Router.navigate usage, link handling",
92
- "tags": ["migration", "LINK", "LIST_OF_LINK", "subLinks", "navigation", "menu", "decision", "COMPONENT_LIST"]
234
+ "tags": [
235
+ "migration",
236
+ "LINK",
237
+ "LIST_OF_LINK",
238
+ "subLinks",
239
+ "navigation",
240
+ "menu",
241
+ "decision",
242
+ "COMPONENT_LIST"
243
+ ]
93
244
  }
94
245
  }
95
- }
246
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-11T10:11:46.509Z",
2
+ "generatedAt": "2026-05-11T12:29:24.468Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "fbp_initAdvancedMatching",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-05-11T10:11:46.537Z",
2
+ "generatedAt": "2026-05-11T12:29:24.493Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "IkasProductAttributeDetail",