@ikas/code-components-mcp 2.1.0 → 2.2.1
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.
- package/data/framework.json +22 -5
- package/data/migration-examples/complex-header-migration/_meta.json +4 -0
- package/data/migration-examples/complex-header-migration/after-config-snippet.json +55 -0
- package/data/migration-examples/complex-header-migration/after-section.tsx +64 -0
- package/data/migration-examples/complex-header-migration/before-props-summary.json +42 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/_meta.json +4 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-child-styles.css +38 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-child.tsx +22 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-config-snippet.json +31 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-section-styles.css +25 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-section.tsx +17 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/before-component.tsx +32 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/before-theme-snippet.json +53 -0
- package/data/migration-examples/full-component-with-tailwind/_meta.json +4 -0
- package/data/migration-examples/full-component-with-tailwind/after-component.tsx +43 -0
- package/data/migration-examples/full-component-with-tailwind/after-config-snippet.json +25 -0
- package/data/migration-examples/full-component-with-tailwind/after-styles.css +99 -0
- package/data/migration-examples/full-component-with-tailwind/before-component.tsx +60 -0
- package/data/migration-examples/object-custom-to-inline-props/_meta.json +4 -0
- package/data/migration-examples/object-custom-to-inline-props/after-component.tsx +34 -0
- package/data/migration-examples/object-custom-to-inline-props/after-config-snippet.json +23 -0
- package/data/migration-examples/object-custom-to-inline-props/after-styles.css +38 -0
- package/data/migration-examples/object-custom-to-inline-props/before-component.tsx +30 -0
- package/data/migration-examples/object-custom-to-inline-props/before-theme-snippet.json +26 -0
- package/data/migration-examples/slider-library-replacement/_meta.json +4 -0
- package/data/migration-examples/slider-library-replacement/after-child.tsx +13 -0
- package/data/migration-examples/slider-library-replacement/after-config-snippet.json +29 -0
- package/data/migration-examples/slider-library-replacement/after-section.tsx +38 -0
- package/data/migration-examples/slider-library-replacement/after-styles.css +43 -0
- package/data/migration-examples/slider-library-replacement/before-component.tsx +39 -0
- package/data/migration-examples/slider-library-replacement/before-types-snippet.ts +14 -0
- package/data/migration.json +260 -0
- package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +3 -3
- package/data/section-templates/account-info-section/ikas-config-snippet.json +5 -5
- package/data/section-templates/category-images-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/category-list-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/component-renderer/ikas-config-snippet.json +3 -3
- package/data/section-templates/features-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/footer-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Announcements/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Navbar/ikas-config-snippet.json +3 -3
- package/data/section-templates/header-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/hero-slider-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/image-handling/ikas-config-snippet.json +13 -13
- package/data/section-templates/navigation/ikas-config-snippet.json +3 -3
- package/data/section-templates/product-detail-section/children/ProductDetailDescription/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +13 -13
- package/data/section-templates/product-slider-section/ikas-config-snippet.json +3 -3
- package/data/storefront-api.json +98 -1393
- package/data/storefront-types.json +28 -123
- package/dist/index.js +1737 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedAt": "2026-04-13T00:00:00.000Z",
|
|
3
|
+
"topics": {
|
|
4
|
+
"migration-overview": {
|
|
5
|
+
"title": "Old Theme → Code Component Migration Overview",
|
|
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)` → consult `get_migration_guide(\"custom-data-conversion\")` and `get_migration_guide(\"component-composition-decision-guide\")` for prop-shape choices → 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## 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
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"prop-type-mapping": {
|
|
18
|
+
"title": "Prop Type Mapping: Old → New",
|
|
19
|
+
"description": "Complete mapping of old theme.json prop types to new ikas.config.json prop types",
|
|
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 |",
|
|
21
|
+
"tags": [
|
|
22
|
+
"migration",
|
|
23
|
+
"props",
|
|
24
|
+
"types",
|
|
25
|
+
"mapping",
|
|
26
|
+
"SLIDER",
|
|
27
|
+
"CUSTOM",
|
|
28
|
+
"PRODUCT_DETAIL",
|
|
29
|
+
"conversion"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"custom-data-conversion": {
|
|
33
|
+
"title": "Converting CUSTOM Props and customData",
|
|
34
|
+
"description": "How to convert old CUSTOM prop types with customData definitions to new COMPONENT_LIST + child components",
|
|
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 Feature-Parity Rule\n\nMigration is a port, not a redesign. If a customData type carries richer structure than the closest built-in prop type can hold, the answer is a new component — not flattening. Phrases like \"I'll add it later via COMPONENT_LIST\" are a red flag; the data is structured for a reason, and the cleanest time to model that structure is at migration time. Get explicit user approval before dropping any feature, and log the approval in `MIGRATION.md` → `## Notes`.\n\nThis is the rule the rest of this guide operationalizes.\n\n## The Heuristic (Short Version)\n\n- Flat scalar set (e.g. `\"left\" | \"right\" | \"center\"`) → enum prop via `config add-enum`.\n- Structured record with ≥3 fields → component via `config add-component` + `COMPONENT_LIST` on the parent.\n- Structured record with 1–2 fields → usually flatten to scalar props on the parent (and log the decision).\n\nFor the full decision tree (including domain LIST types like `LIST_OF_LINK`/`IMAGE_LIST` and the BOOLEAN-toggle anti-pattern), see `get_migration_guide(\"component-composition-decision-guide\")`.\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## Rendering Lists — COMPONENT_LIST vs Data Props\n\nFor the COMPONENT_LIST (merchant hand-picks items) vs data-prop (`PRODUCT_LIST` / `BLOG_LIST` / `CATEGORY_LIST`, data-driven) distinction, see `get_framework_guide(\"component-renderer-patterns\")`. The same registered child component (e.g. `ProductCard`) is reusable across both patterns.\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
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"library-replacements": {
|
|
49
|
+
"title": "Library Replacement Patterns",
|
|
50
|
+
"description": "How to replace common third-party libraries with vanilla Preact + CSS implementations",
|
|
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```",
|
|
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
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"react-to-preact": {
|
|
71
|
+
"title": "React to Preact Conversion Patterns",
|
|
72
|
+
"description": "Code patterns for converting old React components to new Preact code components",
|
|
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()` — full image-rendering recipes (variant chain, gallery, srcsets, background) live in `get_framework_guide(\"image-handling\")`.\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` | `Record<string, string>` | Inline-style object |\n| `React.FC<Props>` | Remove, use named function | |\n| `React.MouseEvent` | `Event` (cast `e.target` as needed) | `JSX.TargetedMouseEvent<T>` is not reliably resolvable in our tsconfig — prefer plain `Event`. |\n| `React.ChangeEvent<HTMLInputElement>` | `Event` + cast | Preact uses `onInput` (not `onChange`) for text inputs. |\n| `React.FormEvent` | `Event` + cast | |\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\nThe old `<Image>` component does not exist in the new system. Replace with native `<img>` + `getDefaultSrc()` / `getSrc()` / `createMediaSrcset()`. The full recipe (single image, variant chain, gallery, background images, responsive srcsets) lives in `get_framework_guide(\"image-handling\")`.\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 — use plain `Event` and cast (works regardless of tsconfig JSX resolution):\nconst handleInput = (e: Event) => {\n setValue((e.target as HTMLInputElement).value);\n};\n\n// Preact uses `onInput` (not `onChange`) for text inputs:\n// <input onInput={handleInput} />\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```",
|
|
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
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"storefront-import-mapping": {
|
|
89
|
+
"title": "Import Mapping: @ikas/storefront → @ikas/bp-storefront",
|
|
90
|
+
"description": "Maps old @ikas/storefront imports to new @ikas/bp-storefront equivalents",
|
|
91
|
+
"content": "## Old Package → New Package\n\nAll imports change from `@ikas/storefront` to `@ikas/bp-storefront`. **This is NOT a find-and-replace.** Even types with the same name (`IkasImage`, `IkasProduct`, etc.) come from different packages and may have different shapes. Always verify with `get_type_definition` or `get_model_guide` before assuming compatibility.\n\n## Type Rename Table\n\n| Old Import (`@ikas/storefront`) | New Import (`@ikas/bp-storefront`) | Notes |\n|-----------|-----------|-------|\n| `IkasImage`, `IkasVideo`, `IkasNavigationLink`, `IkasProduct`, `IkasProductList`, `IkasCategory`, `IkasCategoryList`, `IkasBrand`, `IkasBlog`, `IkasBlogList`, `IkasBlogCategory`, `IkasBlogCategoryList` | Same name | Different package — shape may differ; verify before assuming. |\n| `IkasSlider` | *(removed)* | Use plain `number`. |\n| `IkasAttributeDetail` | `IkasProductAttributeValue` | Renamed. |\n| `IkasAttributeList` | `IkasProductAttributeValue[]` | Renamed; now a plain array. |\n| `IkasComponentRenderer` | `IkasComponentRenderer` | Same name, different usage — see below. |\n\n## Function / Pattern Rename Table\n\n| Old | New | Notes |\n|-----|-----|-------|\n| `<Image>` component | `getDefaultSrc()` / `getSrc()` + native `<img>` | See `react-to-preact` for image patterns. |\n| `useStore()` hook | Direct imports (`cartStore`, `customerStore`, ...) | Stores are MobX observables; no wrapper hook. |\n| `<IkasComponentRenderer>{children}</...>` | `<IkasComponentRenderer id=\"...\" components={list as any[]} parentProps={props} />` | New props-based API. |\n\nFor all import/observer/hook/event-handling behavior changes, see `get_migration_guide(\"react-to-preact\")` — this topic is intentionally a quick rename reference.\n",
|
|
92
|
+
"tags": [
|
|
93
|
+
"migration",
|
|
94
|
+
"imports",
|
|
95
|
+
"storefront",
|
|
96
|
+
"bp-storefront",
|
|
97
|
+
"IkasSlider",
|
|
98
|
+
"IkasImage",
|
|
99
|
+
"useStore",
|
|
100
|
+
"IkasComponentRenderer"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"theme-json-anatomy": {
|
|
104
|
+
"title": "Old theme.json Structure",
|
|
105
|
+
"description": "Explains the structure of old theme.json files for interpreting analyze_old_theme output",
|
|
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.",
|
|
107
|
+
"tags": [
|
|
108
|
+
"migration",
|
|
109
|
+
"theme-json",
|
|
110
|
+
"structure",
|
|
111
|
+
"anatomy",
|
|
112
|
+
"components",
|
|
113
|
+
"customData",
|
|
114
|
+
"groups",
|
|
115
|
+
"settings",
|
|
116
|
+
"pages"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"component-decomposition-strategy": {
|
|
120
|
+
"title": "Component Decomposition Strategy",
|
|
121
|
+
"description": "How to break old monolithic components into section + child components in the new system",
|
|
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 Props → Pick the Right Shape\n\nFor each CUSTOM prop, do NOT default to `COMPONENT_LIST`. See `get_migration_guide(\"component-composition-decision-guide\")` for the full decision tree. Short version:\n- DYNAMIC_LIST of structured records with ≥3 fields → `COMPONENT_LIST` + new child component (use `filteredComponentIds`).\n- DYNAMIC_LIST of items with 1–2 fields → repeated scalar props or a domain LIST type (`LIST_OF_LINK`, `IMAGE_LIST`, …).\n- OBJECT with ≤3 simple fields (TEXT/BOOLEAN/COLOR/NUMBER) → flatten into direct props on the parent.\n\nExample (flatten):\n- Old: `settings: { backgroundColor: string, showTitle: boolean }` via CUSTOM OBJECT\n- New: `backgroundColor` (COLOR) + `showTitle` (BOOLEAN) 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.",
|
|
123
|
+
"tags": [
|
|
124
|
+
"migration",
|
|
125
|
+
"decomposition",
|
|
126
|
+
"section",
|
|
127
|
+
"component",
|
|
128
|
+
"COMPONENT_LIST",
|
|
129
|
+
"header",
|
|
130
|
+
"footer",
|
|
131
|
+
"strategy"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"complete-project-generation": {
|
|
135
|
+
"title": "Complete Project Generation Guide",
|
|
136
|
+
"description": "How to generate a full code-component project from an old theme in one pass",
|
|
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",
|
|
138
|
+
"tags": [
|
|
139
|
+
"migration",
|
|
140
|
+
"project",
|
|
141
|
+
"generation",
|
|
142
|
+
"ikas-config",
|
|
143
|
+
"global-css",
|
|
144
|
+
"structure",
|
|
145
|
+
"complete"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"settings-conversion": {
|
|
149
|
+
"title": "Theme Settings Conversion",
|
|
150
|
+
"description": "How to convert old theme settings (colors, fonts, favicon) to the new system",
|
|
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",
|
|
152
|
+
"tags": [
|
|
153
|
+
"migration",
|
|
154
|
+
"settings",
|
|
155
|
+
"colors",
|
|
156
|
+
"fonts",
|
|
157
|
+
"css-variables",
|
|
158
|
+
"global-css"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
"finding-new-system-equivalents": {
|
|
162
|
+
"title": "Finding New-System Equivalents for Old APIs",
|
|
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.",
|
|
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",
|
|
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
|
+
]
|
|
182
|
+
},
|
|
183
|
+
"iterative-workflow": {
|
|
184
|
+
"title": "Iterative Migration Workflow (Resumable, Section-by-Section)",
|
|
185
|
+
"description": "The recommended four-phase workflow for migrating large themes across multiple sessions. Use this for any theme with more than ~5 sections.",
|
|
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
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"component-renderer-limitations": {
|
|
199
|
+
"title": "IkasComponentRenderer: Critical Limitations",
|
|
200
|
+
"description": "Essential constraints to understand BEFORE designing sections with COMPONENT_LIST props. These limitations fundamentally shape migration decisions.",
|
|
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",
|
|
202
|
+
"tags": [
|
|
203
|
+
"migration",
|
|
204
|
+
"IkasComponentRenderer",
|
|
205
|
+
"COMPONENT_LIST",
|
|
206
|
+
"limitations",
|
|
207
|
+
"parent-child",
|
|
208
|
+
"privateVarMap",
|
|
209
|
+
"filteredComponentIds"
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
"prop-runtime-shapes": {
|
|
213
|
+
"title": "Runtime Shapes of Prop Types",
|
|
214
|
+
"description": "Exact TypeScript shapes your component receives at runtime for each prop type. Covers common confusions (.data vs .links, .variant vs .product).",
|
|
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
|
+
]
|
|
229
|
+
},
|
|
230
|
+
"link-prop-decision-guide": {
|
|
231
|
+
"title": "LINK Prop Decision Guide",
|
|
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.",
|
|
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(\"component-composition-decision-guide\")` — when COMPONENT_LIST is overkill vs. repeated scalars vs. domain LIST types\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",
|
|
234
|
+
"tags": [
|
|
235
|
+
"migration",
|
|
236
|
+
"LINK",
|
|
237
|
+
"LIST_OF_LINK",
|
|
238
|
+
"subLinks",
|
|
239
|
+
"navigation",
|
|
240
|
+
"menu",
|
|
241
|
+
"decision",
|
|
242
|
+
"COMPONENT_LIST"
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
"component-composition-decision-guide": {
|
|
246
|
+
"title": "When to Use COMPONENT / COMPONENT_LIST (Composition Decision Guide)",
|
|
247
|
+
"description": "Decision tree for choosing between scalar props, domain LIST types, and COMPONENT/COMPONENT_LIST. Applies to both greenfield component design and migration callouts.",
|
|
248
|
+
"content": "Default to the simplest prop shape that fits. Reach for `COMPONENT` / `COMPONENT_LIST` only when each item is a multi-field unit AND the count is dynamic. Over-used child components make themes harder to author.\n\n## Decision Tree (pick the first that matches)\n\n1. **Toggling whether to show optional pieces inside a section?** → `BOOLEAN` props on the parent (`showPrice`, `showVariants`). Do NOT wrap near-empty children in `COMPONENT_LIST` just to flip visibility.\n2. **Fixed, small count (≤4) of items, same shape?** → repeated scalar props on the parent (`title1`/`link1`, `title2`/`link2`, …).\n3. **Dynamic list where each item IS one domain object** (link, image, product, category, brand, blog, blog category, raffle)? → matching domain LIST prop type: `LIST_OF_LINK`, `IMAGE_LIST`, `PRODUCT_LIST`, `CATEGORY_LIST`, `BRAND_LIST`, `BLOG_LIST`, `BLOG_CATEGORY_LIST`, `RAFFLE_LIST`. No child component needed.\n4. **Dynamic list AND each item is a structured record (≥3 fields, or mixed types like image+text+link+color)?** → `COMPONENT_LIST` with a dedicated child.\n5. **Exactly one structured child, not a list?** → `COMPONENT` with one child.\n\n## Sizing Rule\n\n- Child would have **0–2 props** → almost never `COMPONENT_LIST`. Use Branch 1, 2, or 3.\n- Child would have **3–4 props** → `COMPONENT_LIST` is acceptable; check whether scalars or a domain LIST type covers it first.\n- Child would have **5+ props OR mixed prop types** → `COMPONENT_LIST` is correct.\n\n## Anti-Patterns\n\n- List of items where each item is one text field → repeated `TEXT` props.\n- Wrapping 2–3 leaf \"display card\" components purely to toggle visibility → `BOOLEAN` props.\n- List of `{icon, link}` items → `LIST_OF_LINK` (icon derivable from link) or repeated scalars.\n\n## Sanity Check Before You Commit to `COMPONENT_LIST`\n\nAll three should hold:\n- Each child meaningfully renders its own content (its own CSS, handlers, layout).\n- Reordering items in the editor is a real UX win for the store owner.\n- The same child is reusable in multiple parents (or could be).\n\n## Migration Carve-Out\n\nThe Feature-Parity Rule still applies — do NOT silently drop fields. But if an old `customData` has only **1–2 fields**, prefer flattening to scalar props on the parent and log it under `## Custom Data Decisions` in `MIGRATION.md`. An explicit, logged 1–2-field flatten is fine; silent simplification is not. For ≥3-field records, build the component.\n\n## See Also\n\n- `get_migration_guide(\"custom-data-conversion\")` — Feature-Parity Rule + worked ≥3-field examples\n- `get_migration_guide(\"link-prop-decision-guide\")` — link-shaped data specifically\n- `get_prop_types` — full prop type reference including all domain LIST types\n",
|
|
249
|
+
"tags": [
|
|
250
|
+
"composition",
|
|
251
|
+
"COMPONENT_LIST",
|
|
252
|
+
"COMPONENT",
|
|
253
|
+
"decision-guide",
|
|
254
|
+
"greenfield",
|
|
255
|
+
"migration",
|
|
256
|
+
"simplicity"
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"filteredComponentIds": [
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
44
|
+
"<id-of-CardProductPrice>",
|
|
45
|
+
"<id-of-CardProductVariants>",
|
|
46
|
+
"<id-of-CardProductName>"
|
|
47
47
|
]
|
|
48
48
|
}
|
|
49
49
|
],
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"type": "COMPONENT_LIST",
|
|
52
52
|
"required": false,
|
|
53
53
|
"filteredComponentIds": [
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
54
|
+
"<id-of-AccountInfoContent>",
|
|
55
|
+
"<id-of-AccountOrders>",
|
|
56
|
+
"<id-of-AccountAddresses>",
|
|
57
|
+
"<id-of-AccountFavorites>",
|
|
58
|
+
"<id-of-AccountOrderDetail>"
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
{
|
|
@@ -178,9 +178,9 @@
|
|
|
178
178
|
}
|
|
179
179
|
},
|
|
180
180
|
"filteredComponentIds": [
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
181
|
+
"<id-of-CardProductPrice>",
|
|
182
|
+
"<id-of-CardProductVariants>",
|
|
183
|
+
"<id-of-CardProductName>"
|
|
184
184
|
]
|
|
185
185
|
},
|
|
186
186
|
{
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"type": "COMPONENT_LIST",
|
|
12
12
|
"required": false,
|
|
13
13
|
"filteredComponentIds": [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"<id-of-Navbar>",
|
|
15
|
+
"<id-of-Announcements>",
|
|
16
|
+
"<id-of-CookieBar>"
|
|
17
17
|
]
|
|
18
18
|
},
|
|
19
19
|
{
|
|
@@ -226,9 +226,9 @@
|
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
228
|
"filteredComponentIds": [
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
229
|
+
"<id-of-CardProductPrice>",
|
|
230
|
+
"<id-of-CardProductVariants>",
|
|
231
|
+
"<id-of-CardProductName>"
|
|
232
232
|
]
|
|
233
233
|
},
|
|
234
234
|
{
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"type": "COMPONENT_LIST",
|
|
12
12
|
"required": false,
|
|
13
13
|
"filteredComponentIds": [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"<id-of-Navbar>",
|
|
15
|
+
"<id-of-Announcements>",
|
|
16
|
+
"<id-of-CookieBar>"
|
|
17
17
|
]
|
|
18
18
|
},
|
|
19
19
|
{
|