@ikas/code-components-mcp 2.9.8 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -636,7 +636,7 @@
636
636
  "theme-globals": {
637
637
  "title": "Theme Global Settings & Design Tokens",
638
638
  "description": "Read and create the theme's global variables and design tokens (colors, typography, breakpoints, keyframes, color schemes) from code components",
639
- "content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className }`; breakpoints → `{ id, name, width }`; keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable,\n// so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, the theme globals it uses are detected at publish time and travel with it. Publish scans the component's compiled output AND the shared chunks it imports; each detected global is added to the asset and created in the destination store on install (unless it is already there), so the references in your code keep resolving there.\n\n- **Global variables travel — hardcoded keys included.** A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` / `getThemeSettingValue(\"_xyz\")` call with a literal key ships exactly those variables — default value, groups and types included — also when the call lives in a shared helper file. If no literal key is visible (the key is computed or passed in as a value), or the component calls the iterate-all `getThemeSettings()`, ALL global variables ship. A prop bound to a global variable (via its `privateVarMap`) ships too. You do NOT need to turn a setting into a prop just to make it portable.\n- **Don't mix literal and runtime-built keys.** As soon as ANY literal key is visible in the component's compiled code or the shared chunks it imports, only the literal keys ship — a key built at runtime (`\"_\" + id`, a key read from a prop or another value) is then NOT shipped and resolves to nothing in the destination store. Keep every key literal, or read through `getThemeSettings()`.\n- **Colors, typography, keyframes and breakpoints travel too.** A literal reference in your CSS/JS — a color's `cssVar`, a typography `className` (`_<id>`), a keyframe `ref` (`_<id>`), a `bp(<id>)` token — is detected and ships; the iterate-all accessors (`getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`) ship the whole category. Iterate the runtime lists when the component should adapt to whatever tokens the destination theme has, rather than to the ones it was built against.\n- **Color schemes always travel in full.** Every scheme slot and palette ships with the asset and is matched by id on install, never by name — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.\n- **Also travels automatically:** types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: reference globals by the ids/keys `list_theme_globals` returns (never by display name) and they ship with the asset — do not tell the user they must recreate them in the destination store.",
639
+ "content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className, breakpoints? }`; breakpoints → `{ id, name, width }` (widest first); keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable,\n// so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\n**Responsive text styles:** `update_text_style` with `breakpoint_id` (an existing breakpoint id from `list_theme_globals`) sets that breakpoint's overrides instead of the desktop values — only `font_size`, `font_weight`, `line_height`, `letter_spacing`; pass `\"\"` to remove an override. CLI: `update-text-style --id <id> --breakpoint <breakpointId> --font-size 32px`. For several breakpoints in ONE call pass `breakpoints: [{ breakpoint_id, font_size?, font_weight?, line_height?, letter_spacing? }]` to `update_text_style` or to `create_theme_global` (kind `typography`, together with the desktop values) — CLI `--breakpoints '[{\"breakpointId\":\"<id>\",\"fontSize\":\"32px\"}]'` on `create-text-style` / `update-text-style`; the whole batch is validated and one invalid entry writes nothing. The editor rejects invalid writes (unknown breakpoint, other properties, italic weight, a property without a desktop value, malformed values, weights the font lacks) — fix the input and retry; breakpoints are not created by this call (use `create_theme_global` kind `breakpoint`).\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n Text styles can be responsive (per-breakpoint font size, line height, letter spacing and font weight). Apply them with `className` (or the literal `_<id>` class) to get per-breakpoint values automatically. `resolved` contains desktop values only; a responsive style also has `breakpoints: [{ id, width, overrides, resolved }]` (widest first, only breakpoints with own overrides; `overrides` = that breakpoint's own values, `resolved` = full effective style there). Inheritance is chained: a property a breakpoint does not override comes from the nearest WIDER breakpoint that overrides it, else from desktop. Overriding font weight keeps the style's italic (italic is not per breakpoint). `text_overflow: ellipsis` only truncates together with `white_space: nowrap` (a text style property) and `overflow: hidden` (component CSS — not a text style property). The field is absent when the style has no overrides. When a component is published to another store, the breakpoints its text style overrides use are shipped with it.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, the theme globals it uses are detected at publish time and travel with it. Publish scans the component's compiled output AND the shared chunks it imports; each detected global is added to the asset and created in the destination store on install (unless it is already there), so the references in your code keep resolving there.\n\n- **Global variables travel — hardcoded keys included.** A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` / `getThemeSettingValue(\"_xyz\")` call with a literal key ships exactly those variables — default value, groups and types included — also when the call lives in a shared helper file. If no literal key is visible (the key is computed or passed in as a value), or the component calls the iterate-all `getThemeSettings()`, ALL global variables ship. A prop bound to a global variable (via its `privateVarMap`) ships too. You do NOT need to turn a setting into a prop just to make it portable.\n- **Don't mix literal and runtime-built keys.** As soon as ANY literal key is visible in the component's compiled code or the shared chunks it imports, only the literal keys ship — a key built at runtime (`\"_\" + id`, a key read from a prop or another value) is then NOT shipped and resolves to nothing in the destination store. Keep every key literal, or read through `getThemeSettings()`.\n- **Colors, typography, keyframes and breakpoints travel too.** A literal reference in your CSS/JS — a color's `cssVar`, a typography `className` (`_<id>`), a keyframe `ref` (`_<id>`), a `bp(<id>)` token — is detected and ships; the iterate-all accessors (`getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`) ship the whole category. Iterate the runtime lists when the component should adapt to whatever tokens the destination theme has, rather than to the ones it was built against.\n- **Color schemes always travel in full.** Every scheme slot and palette ships with the asset and is matched by id on install, never by name — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.\n- **Also travels automatically:** types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: reference globals by the ids/keys `list_theme_globals` returns (never by display name) and they ship with the asset — do not tell the user they must recreate them in the destination store.",
640
640
  "tags": [
641
641
  "theme",
642
642
  "global variables",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-04T12:50:58.225Z",
2
+ "generatedAt": "2026-10-14T08:11:46.617Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "apiListProductBrand",
@@ -13507,6 +13507,171 @@
13507
13507
  "IkasCustomerStore"
13508
13508
  ]
13509
13509
  },
13510
+ {
13511
+ "name": "milesSmilesBind",
13512
+ "signature": "async function milesSmilesBind(customerStore: IkasCustomerStore, redirect?: string | null): Promise<MilesSmilesBindResult>",
13513
+ "description": "Start the Turkish Airlines Miles&Smiles account bind flow for the logged-in customer.\nOpens the THY login page in a popup; once THY confirms, the browser is sent to the\nikas bind callback and lands back on `redirect` with `milesSmilesStatus` query params\n(see handleMilesSmilesBind). Binding is required before miles can be spent at checkout.",
13514
+ "params": [
13515
+ {
13516
+ "name": "customerStore",
13517
+ "description": "- The customer store instance (customer must be logged in)"
13518
+ },
13519
+ {
13520
+ "name": "redirect",
13521
+ "description": "- Relative store path to land on after bind, e.g. \"/account\" or \"/checkout?id=...&step=payment\""
13522
+ }
13523
+ ],
13524
+ "returns": "`isSuccess: true` when the bind hand-off started; otherwise an `errorCode`\nsuch as \"AUTHORIZATION_REQUIRED\" or \"MILES_SMILES_LOGIN_CANCELLED_OR_FAILED\"",
13525
+ "returnType": "Promise<MilesSmilesBindResult>",
13526
+ "example": "```typescript\nimport { customerStore, milesSmilesBind } from \"@ikas/bp-storefront\";\n\nasync function onLinkClick() {\n const result = await milesSmilesBind(customerStore, \"/account\");\n if (!result.isSuccess) showToast(result.errorCode || \"UNEXPECTED_ERROR\");\n}\n```",
13527
+ "categories": [
13528
+ "Customer",
13529
+ "Miles&Smiles"
13530
+ ],
13531
+ "related": [
13532
+ "handleMilesSmilesBind",
13533
+ "getMilesSmilesLink",
13534
+ "unlinkMilesSmiles",
13535
+ "getMilesSmilesDetails"
13536
+ ],
13537
+ "isAsync": true,
13538
+ "isClass": false,
13539
+ "parameterTypes": [
13540
+ "IkasCustomerStore"
13541
+ ]
13542
+ },
13543
+ {
13544
+ "name": "handleMilesSmilesBind",
13545
+ "signature": "function handleMilesSmilesBind(clear: boolean = true): HandleMilesSmilesBindReturnType | null",
13546
+ "description": "Read the Miles&Smiles bind result from the current URL after THY redirects back\n(`?milesSmilesStatus=success` or `?milesSmilesStatus=fail&milesSmilesError=CODE`).\nCall this on the landing page (account or checkout). The params are removed from the\nURL so a refresh does not repeat the same message.",
13547
+ "params": [
13548
+ {
13549
+ "name": "clear",
13550
+ "description": "- Remove the params from the URL with history.replaceState (default: true)"
13551
+ }
13552
+ ],
13553
+ "returns": "The landing result, or null when the URL carries no bind result",
13554
+ "returnType": "HandleMilesSmilesBindReturnType | null",
13555
+ "example": "```typescript\nimport { handleMilesSmilesBind } from \"@ikas/bp-storefront\";\n\nuseEffect(() => {\n const result = handleMilesSmilesBind();\n if (result?.status === \"success\") showToast(\"Miles&Smiles account linked\");\n if (result?.status === \"fail\") showToast(result.errorCode || \"Bind failed\");\n}, []);\n```",
13556
+ "categories": [
13557
+ "Customer",
13558
+ "Miles&Smiles"
13559
+ ],
13560
+ "related": [
13561
+ "milesSmilesBind",
13562
+ "getMilesSmilesLink"
13563
+ ],
13564
+ "isAsync": false,
13565
+ "isClass": false
13566
+ },
13567
+ {
13568
+ "name": "getMilesSmilesLink",
13569
+ "signature": "async function getMilesSmilesLink(customerStore: IkasCustomerStore): Promise<APIResponse<ExternalAccountLink | null>>",
13570
+ "description": "Get the customer's durable Miles&Smiles link (TK membership number and link date).\nA link does not guarantee a live checkout session; when getMilesSmilesDetails returns\nMILES_SMILES_LOGIN_REQUIRED, run milesSmilesBind again.",
13571
+ "params": [
13572
+ {
13573
+ "name": "customerStore",
13574
+ "description": "- The customer store instance"
13575
+ }
13576
+ ],
13577
+ "returns": "API response whose data is the link, or null when not linked",
13578
+ "returnType": "Promise<APIResponse<ExternalAccountLink | null>>",
13579
+ "example": "```typescript\nimport { customerStore, getMilesSmilesLink } from \"@ikas/bp-storefront\";\n\nconst response = await getMilesSmilesLink(customerStore);\nconst tkNumber = response.data?.externalId;\n```",
13580
+ "categories": [
13581
+ "Customer",
13582
+ "Miles&Smiles"
13583
+ ],
13584
+ "related": [
13585
+ "milesSmilesBind",
13586
+ "unlinkMilesSmiles"
13587
+ ],
13588
+ "isAsync": true,
13589
+ "isClass": false,
13590
+ "parameterTypes": [
13591
+ "IkasCustomerStore"
13592
+ ]
13593
+ },
13594
+ {
13595
+ "name": "unlinkMilesSmiles",
13596
+ "signature": "async function unlinkMilesSmiles(customerStore: IkasCustomerStore): Promise<APIResponse<boolean>>",
13597
+ "description": "Remove the customer's Miles&Smiles link. Also clears the server-side checkout\nsession token, so miles cannot be spent until the customer binds again.",
13598
+ "params": [
13599
+ {
13600
+ "name": "customerStore",
13601
+ "description": "- The customer store instance"
13602
+ }
13603
+ ],
13604
+ "returns": "API response whose data is true on success",
13605
+ "returnType": "Promise<APIResponse<boolean>>",
13606
+ "example": "```typescript\nimport { customerStore, unlinkMilesSmiles } from \"@ikas/bp-storefront\";\n\nconst response = await unlinkMilesSmiles(customerStore);\nif (response.isSuccess) showToast(\"Miles&Smiles link removed\");\n```",
13607
+ "categories": [
13608
+ "Customer",
13609
+ "Miles&Smiles"
13610
+ ],
13611
+ "related": [
13612
+ "milesSmilesBind",
13613
+ "getMilesSmilesLink"
13614
+ ],
13615
+ "isAsync": true,
13616
+ "isClass": false,
13617
+ "parameterTypes": [
13618
+ "IkasCustomerStore"
13619
+ ]
13620
+ },
13621
+ {
13622
+ "name": "getMilesSmilesDetails",
13623
+ "signature": "async function getMilesSmilesDetails(customerStore: IkasCustomerStore): Promise<APIResponse<MilesSmilesDetailsResponse | undefined>>",
13624
+ "description": "Get the Miles&Smiles balance and member details for the payment step.\nRequires a recent bind; MILES_SMILES_LOGIN_REQUIRED means the short-lived session\nexpired and milesSmilesBind should be run again. MILES_SMILES_NOT_ENABLED and\nPAYMENT_GATEWAY_NOT_FOUND mean the Miles UI should be hidden.",
13625
+ "params": [
13626
+ {
13627
+ "name": "customerStore",
13628
+ "description": "- The customer store instance"
13629
+ }
13630
+ ],
13631
+ "returns": "API response whose data holds availableMiles, memberId, firstName and lastName",
13632
+ "returnType": "Promise<APIResponse<MilesSmilesDetailsResponse | undefined>>",
13633
+ "example": "```typescript\nimport { customerStore, getMilesSmilesDetails } from \"@ikas/bp-storefront\";\n\nconst response = await getMilesSmilesDetails(customerStore);\nif (response.errorCodes.includes(\"MILES_SMILES_LOGIN_REQUIRED\")) {\n await milesSmilesBind(customerStore, window.location.pathname + window.location.search);\n} else if (response.data) {\n console.log(\"Available miles:\", response.data.availableMiles);\n}\n```",
13634
+ "categories": [
13635
+ "Customer",
13636
+ "Miles&Smiles"
13637
+ ],
13638
+ "related": [
13639
+ "milesSmilesBind",
13640
+ "getMilesSmilesLink"
13641
+ ],
13642
+ "isAsync": true,
13643
+ "isClass": false,
13644
+ "parameterTypes": [
13645
+ "IkasCustomerStore"
13646
+ ]
13647
+ },
13648
+ {
13649
+ "name": "isMilesSmilesTransaction",
13650
+ "signature": "function isMilesSmilesTransaction(transaction: IkasOrderTransaction): boolean",
13651
+ "description": "Check whether an order transaction is the Miles&Smiles leg of a multi-tender payment.\nSuch rows are written on the synthetic MILES_SMILES gateway and are not refundable from the storefront.",
13652
+ "params": [
13653
+ {
13654
+ "name": "transaction",
13655
+ "description": "- The order transaction to check"
13656
+ }
13657
+ ],
13658
+ "returns": "True when the transaction is a Miles&Smiles SALE",
13659
+ "returnType": "boolean",
13660
+ "example": "```typescript\nimport { isMilesSmilesTransaction } from \"@ikas/bp-storefront\";\n\nconst label = isMilesSmilesTransaction(tx) ? \"Miles&Smiles\" : tx.paymentGatewayName;\n```",
13661
+ "categories": [
13662
+ "Customer",
13663
+ "Orders",
13664
+ "Miles&Smiles"
13665
+ ],
13666
+ "related": [
13667
+ "getOrderTransactions"
13668
+ ],
13669
+ "isAsync": false,
13670
+ "isClass": false,
13671
+ "parameterTypes": [
13672
+ "IkasOrderTransaction"
13673
+ ]
13674
+ },
13510
13675
  {
13511
13676
  "name": "deleteCustomerAddress",
13512
13677
  "signature": "async function deleteCustomerAddress(customerStore: IkasCustomerStore, address: IkasCustomerAddress): Promise<boolean>",
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-04T12:50:58.251Z",
2
+ "generatedAt": "2026-10-14T08:11:46.690Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "TextElementTagTypes",
@@ -8078,6 +8078,7 @@
8078
8078
  "relatedFunctions": [
8079
8079
  "getOrderTransactionFormattedAmount",
8080
8080
  "getOrderTransactionPaymentMethodTranslation",
8081
+ "isMilesSmilesTransaction",
8081
8082
  "getOrderTransactions"
8082
8083
  ]
8083
8084
  },
@@ -8319,6 +8320,12 @@
8319
8320
  "type": "string | null",
8320
8321
  "optional": false
8321
8322
  },
8323
+ {
8324
+ "name": "milesSmilesEnabled",
8325
+ "type": "boolean | null",
8326
+ "optional": true,
8327
+ "description": "True when Miles&Smiles can be redeemed on this (TK_PAY) gateway."
8328
+ },
8322
8329
  {
8323
8330
  "name": "name",
8324
8331
  "type": "string",
@@ -14692,6 +14699,44 @@
14692
14699
  "type": "boolean",
14693
14700
  "optional": false,
14694
14701
  "description": "True when this is the theme's default/global text style (the editor's \"Make Default Style\")."
14702
+ },
14703
+ {
14704
+ "name": "breakpoints",
14705
+ "type": "TypographyBreakpointToken[]",
14706
+ "optional": true,
14707
+ "description": "Per-breakpoint values, widest first; present only when the style overrides at least one\nbreakpoint. `className` already applies them through media queries — read this only when a\nvalue is needed in JS. Breakpoints without their own overrides are omitted: they use the next\nwider listed breakpoint, or the desktop `resolved` values."
14708
+ }
14709
+ ],
14710
+ "filePath": "theme-globals.ts"
14711
+ },
14712
+ {
14713
+ "name": "TypographyBreakpointToken",
14714
+ "kind": "type",
14715
+ "domain": "theme",
14716
+ "properties": [
14717
+ {
14718
+ "name": "id",
14719
+ "type": "string",
14720
+ "optional": false,
14721
+ "description": "Theme breakpoint id (see `BreakpointToken`)."
14722
+ },
14723
+ {
14724
+ "name": "width",
14725
+ "type": "number",
14726
+ "optional": false,
14727
+ "description": "Media query max-width in px."
14728
+ },
14729
+ {
14730
+ "name": "overrides",
14731
+ "type": "Record<string, string>",
14732
+ "optional": false,
14733
+ "description": "Only the values this breakpoint sets itself, keyed like `resolved`."
14734
+ },
14735
+ {
14736
+ "name": "resolved",
14737
+ "type": "Record<string, string>",
14738
+ "optional": false,
14739
+ "description": "Full effective values at this breakpoint: desktop values plus inherited and own overrides."
14695
14740
  }
14696
14741
  ],
14697
14742
  "filePath": "theme-globals.ts"
@@ -14993,6 +15038,11 @@
14993
15038
  "name": "isDefault",
14994
15039
  "type": "boolean",
14995
15040
  "optional": true
15041
+ },
15042
+ {
15043
+ "name": "breakpointStyles",
15044
+ "type": "{ breakpointId: string; styles?: ElementStyleLike[] }[]",
15045
+ "optional": true
14996
15046
  }
14997
15047
  ],
14998
15048
  "filePath": "theme-globals.ts"
@@ -15084,7 +15134,7 @@
15084
15134
  },
15085
15135
  {
15086
15136
  "name": "width",
15087
- "type": "number",
15137
+ "type": "number | string",
15088
15138
  "optional": true
15089
15139
  }
15090
15140
  ],
package/dist/index.js CHANGED
@@ -4682,7 +4682,7 @@ const textStyleFontParams = {
4682
4682
  text_overflow: z
4683
4683
  .string()
4684
4684
  .optional()
4685
- .describe("text-overflow: clip | ellipsis"),
4685
+ .describe("text-overflow: clip | ellipsis — ellipsis only truncates with white_space: nowrap and overflow: hidden in the component CSS"),
4686
4686
  white_space: z
4687
4687
  .string()
4688
4688
  .optional()
@@ -4717,13 +4717,48 @@ function textStyleFontArgs(input) {
4717
4717
  "--word-break": input.word_break,
4718
4718
  "--text-wrap-style": input.text_wrap_style,
4719
4719
  };
4720
- return Object.entries(map).flatMap(([flag, value]) => value ? [flag, value] : []);
4720
+ // An empty string is passed through: with breakpoint_id it removes that override.
4721
+ return Object.entries(map).flatMap(([flag, value]) => value != null ? [flag, value] : []);
4722
+ }
4723
+ // Per-breakpoint text-style overrides in one call. `.strict()` so a non-responsive property
4724
+ // (e.g. text_transform) is rejected instead of silently dropped.
4725
+ const textStyleBreakpointsParam = z
4726
+ .array(z
4727
+ .object({
4728
+ breakpoint_id: z
4729
+ .string()
4730
+ .describe("Existing theme breakpoint id from `list_theme_globals`."),
4731
+ font_size: z.string().optional(),
4732
+ font_weight: z
4733
+ .string()
4734
+ .optional()
4735
+ .describe('Plain number, e.g. "500" (the style\'s italic is kept).'),
4736
+ line_height: z.string().optional(),
4737
+ letter_spacing: z.string().optional(),
4738
+ })
4739
+ .strict())
4740
+ .optional()
4741
+ .describe("RESPONSIVE overrides for several breakpoints in ONE call: [{ breakpoint_id, font_size?, font_weight?, " +
4742
+ "line_height?, letter_spacing? }]. Only these 4 properties, each needs a desktop value (set in this same " +
4743
+ 'call or earlier), "" removes an override. Validated as a whole: one invalid entry rejects the call and ' +
4744
+ "nothing is written. Breakpoints are not created here.");
4745
+ function textStyleBreakpointsArgs(breakpoints) {
4746
+ if (!breakpoints)
4747
+ return [];
4748
+ const json = breakpoints.map((bp) => ({
4749
+ breakpointId: bp.breakpoint_id,
4750
+ fontSize: bp.font_size,
4751
+ fontWeight: bp.font_weight,
4752
+ lineHeight: bp.line_height,
4753
+ letterSpacing: bp.letter_spacing,
4754
+ }));
4755
+ return ["--breakpoints", JSON.stringify(json)];
4721
4756
  }
4722
4757
  // Tool: create_theme_global
4723
4758
  server.tool("create_theme_global", "Create a theme global setting in the connected editor. `kind` selects what to create:\n" +
4724
4759
  '- globalVariable: requires display_name + type (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); value optional. Pass `value` as a REAL JSON value, never a stringified JSON. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: real JSON boolean true/false (NOT the string "true"); IMAGE: an image REFERENCE object { id: "theme-images/<uuid>" } (NOT { url }) — the id MUST come from a REAL upload: call `upload_image` (file_path or image_url) and use the id it returns, or reuse an id already listed by `list_theme_globals`. NEVER invent an id — it is stored as given and renders as a broken image, so if you have no image, omit `value` and let the merchant set it from the "Theme Settings" panel; BORDER: { width: { value, unit }, style, color } (width.value must be a number); SHADOW: { x, y, blur, spread, color, position: "outside"|"inside" }. For object types (IMAGE/BORDER/SHADOW) pass an actual JSON object, not a JSON-encoded string. Every value is validated server-side and a wrong shape is REJECTED, not silently stored.\n' +
4725
4760
  '- color: requires name + value — a CONCRETE color (hex e.g. "#ff0000", rgb()/hsl() or a CSS color name). A color token cannot alias another color, so a "var(...)" reference is REJECTED here (to reuse another theme color, reference its cssVar in component code, or in a colorScheme slot). TIP: a "/" in the `name` GROUPS the color in the editor UI (e.g. "Brand/Primary", "Brand/Accent" appear under a "Brand" group); use this to organize related colors. To CHANGE an existing color later, use `update_theme_color` (edits in place, keeps the cssVar stable) — do NOT delete+recreate.\n' +
4726
- '- typography: requires name + at least one text-style property. These are exactly the fields the editor\'s "Update Text Style" modal exposes — nothing else (in particular there is NO color or text-align on a text style). VALUE FORMATS (all validated; a bad value is rejected with the offending value and the allowed set): font_weight is one of 100,200,300,400,500,600,700,800,900 (append \'i\' for italic, e.g. "600i") AND must be a weight the chosen font actually ships (many fonts provide only a subset, e.g. "Modak" only ships 400) — if unavailable the call fails and lists the supported weights, so when unsure use 400; stored as a numeric weight + a font-style entry; font_size is a number WITH a unit (e.g. "16px", "1.2rem", "80%"); line_height is "normal", a unitless number (e.g. "1.5") or a length; letter_spacing is "normal" or a length (e.g. "0.02em", "0.5px"); font_family is a real font the editor offers (a Google Fonts name with EXACT casing, e.g. "Inter") — an unknown/invented family is rejected; text_decoration is none|underline|line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis; white_space is normal|pre|pre-wrap|pre-line|wrap|nowrap|collapse; line_break is auto|loose|normal|strict|anywhere; word_break is normal|break-all|keep-all; text_wrap_style is auto|balance|pretty|stable. TIP: a "/" in the `name` GROUPS the text style in the editor UI (e.g. "Heading/H1", "Heading/H2" appear under a "Heading" group); use this to organize related styles. To CHANGE an existing text style later, use `update_text_style` (edits in place, keeps the `className` stable) — do NOT delete+recreate, which changes the id/className.\n' +
4761
+ '- typography: requires name + at least one text-style property. These are exactly the fields the editor\'s "Update Text Style" modal exposes — nothing else (in particular there is NO color or text-align on a text style). VALUE FORMATS (all validated; a bad value is rejected with the offending value and the allowed set): font_weight is one of 100,200,300,400,500,600,700,800,900 (append \'i\' for italic, e.g. "600i") AND must be a weight the chosen font actually ships (many fonts provide only a subset, e.g. "Modak" only ships 400) — if unavailable the call fails and lists the supported weights, so when unsure use 400; stored as a numeric weight + a font-style entry; font_size is a number WITH a unit (e.g. "16px", "1.2rem", "80%"); line_height is "normal", a unitless number (e.g. "1.5") or a length; letter_spacing is "normal" or a length (e.g. "0.02em", "0.5px"); font_family is a real font the editor offers (a Google Fonts name with EXACT casing, e.g. "Inter") — an unknown/invented family is rejected; text_decoration is none|underline|line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis; white_space is normal|pre|pre-wrap|pre-line|wrap|nowrap|collapse; line_break is auto|loose|normal|strict|anywhere; word_break is normal|break-all|keep-all; text_wrap_style is auto|balance|pretty|stable. TIP: a "/" in the `name` GROUPS the text style in the editor UI (e.g. "Heading/H1", "Heading/H2" appear under a "Heading" group); use this to organize related styles. Optional `breakpoints` sets responsive overrides in the same call (see the param). To CHANGE an existing text style later, use `update_text_style` (edits in place, keeps the `className` stable) — do NOT delete+recreate, which changes the id/className.\n' +
4727
4762
  "- breakpoint: requires name + width (a positive INTEGER, in px — no decimals). To CHANGE an existing breakpoint later, use `update_theme_breakpoint` (edits in place, keeps the id).\n" +
4728
4763
  "- keyframe: requires name + points (array of { point, styles? }) where each style is { property, value } using a CSS property name (opacity, transform, filter, background, color, …); keyframe_type defaults to \"keyframe\". Optional `settings` = animation-level config as { property, value } entries, TYPE-DEPENDENT: for keyframe — animation-duration, animation-iteration-count, animation-play-state, animation-delay, animation-timing-function, animation-direction, animation-fill-mode, animation-timeline, animation-range, transform-origin, backface-visibility; for transition — transition, backface-visibility. Setting these means the editor drives the animation timing (you don't have to hand-write `animation:` CSS). Use the keyframe's `ref` as the CSS animation-name. For a TRANSITION keyframe, put the target/hover-state styles on the 100% point (points[1].styles) — the 0% point is ignored (the start state is the element's own normal state). To CHANGE an existing keyframe later, use `update_theme_keyframe` (edits in place, keeps the `ref` stable) — do NOT delete+recreate, which changes the ref and breaks references.\n" +
4729
4764
  '- colorScheme: requires name + colors (array of { slotId? | newSlotName?, value }) — each color targets ONE color SLOT, EITHER by `slotId` (an EXISTING slot\'s id, from `list_theme_globals` → colorSchemes.schemes[].id or a `colorsByScheme` key — the id-keyed path) OR by `newSlotName` (a name to CREATE-or-reuse a slot; write-only, for the standard vocabulary and brand-new slots). Pass EXACTLY ONE of the two per entry. `value` is EITHER a literal color (hex/rgb/hsl or a CSS color name, e.g. "#0b1120") OR a REFERENCE to an existing theme color, passed as that color\'s cssVar from `list_theme_globals` (e.g. "var(--<colorId>)"). A `var(--id)` value is stored as a linked reference (editing the global color updates the scheme too and the dependency is tracked) — do NOT inline a hex you copied from a global color; pass its cssVar. SLOT IDENTITY: `colorSchemes.schemes` lists each slot as `{id, name}`. To set a color on an EXISTING slot, find it there by name and pass its `slotId` (names are NOT unique — two design assets can each have a "Background" — so when ambiguous disambiguate via the scheme\'s existing `colorsByScheme` keys); use `newSlotName` only to add a slot the scheme does not have yet. When creating slots use the theme\'s STANDARD slot names so components resolve them: "Background", "Heading", "Text", "Link", "HoverLink", "Border", and per button "PrimaryButton/Background", "PrimaryButton/Text", "PrimaryButton/Border", "PrimaryButton/HoverBackground", "PrimaryButton/HoverText", "PrimaryButton/HoverBorder" (and the same six under "SecondaryButton/"); a "/" groups the slot in the editor UI. A scheme should normally define EVERY slot it uses; omitted slots fall back to the default scheme\'s color. A mistaken slot can be removed with `delete_theme_global` kind `colorSchemeSlot` (by slot id), or renamed in place (id stays stable) with `rename_theme_color_scheme_slot`. To CHANGE an existing scheme later, use `update_theme_color_scheme` (edits in place, keeps the className stable; merges colors by slot, and can set/clear its default flag via `is_default`).\n' +
@@ -4752,6 +4787,7 @@ server.tool("create_theme_global", "Create a theme global setting in the connect
4752
4787
  .optional()
4753
4788
  .describe("globalVariable default value (shape depends on type — see tool description), or color hex string."),
4754
4789
  ...textStyleFontParams,
4790
+ breakpoints: textStyleBreakpointsParam.describe("typography only: " + textStyleBreakpointsParam.description),
4755
4791
  width: z
4756
4792
  .number()
4757
4793
  .int()
@@ -4804,6 +4840,7 @@ server.tool("create_theme_global", "Create a theme global setting in the connect
4804
4840
  "--name",
4805
4841
  String(input.name ?? ""),
4806
4842
  ...textStyleFontArgs(input),
4843
+ ...textStyleBreakpointsArgs(input.breakpoints),
4807
4844
  ...p,
4808
4845
  ];
4809
4846
  break;
@@ -4956,12 +4993,20 @@ server.tool("update_text_style", "Update an existing theme typography token (tex
4956
4993
  'or a length; letter_spacing is "normal" or a length (e.g. "0.02em"); font_family is a real font the editor ' +
4957
4994
  'offers (a Google Fonts name with EXACT casing, e.g. "Inter") — an unknown/invented family is rejected; ' +
4958
4995
  "text_decoration is none|underline|" +
4959
- "line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis; white_space " +
4996
+ "line-through; text_transform is none|uppercase|lowercase|capitalize; text_overflow is clip|ellipsis (ellipsis also needs white_space: nowrap here and overflow: hidden in the component CSS); white_space " +
4960
4997
  "is normal|pre|pre-wrap|pre-line|wrap|nowrap|collapse; line_break is auto|loose|normal|strict|anywhere; word_break " +
4961
4998
  "is normal|break-all|keep-all; text_wrap_style is auto|balance|pretty|stable. Set `is_default: true` to make " +
4962
4999
  "this text style the theme's default/global style (the editor's right-click \"Make Default Style\" / \"Global " +
4963
5000
  "Stil Yap\") — any other default in the group is cleared; `is_default: false` removes the flag. A text style's " +
4964
- "current default state is readable from `list_theme_globals` (each typography entry's `isDefault`). Requires " +
5001
+ "current default state is readable from `list_theme_globals` (each typography entry's `isDefault`). " +
5002
+ "RESPONSIVE: pass `breakpoint_id` (an existing breakpoint id from `list_theme_globals`) to set that breakpoint's " +
5003
+ "overrides instead of the desktop values. Then ONLY font_size, font_weight, line_height and letter_spacing are " +
5004
+ "allowed, each needs a desktop value on the style first, font_weight is a plain number (the style's italic is kept — italic is " +
5005
+ 'shared by all breakpoints) and an empty string ("") removes that override so the value is inherited again. Overrides cascade ' +
5006
+ "desktop-first and chained: a property a breakpoint does not override comes from the nearest WIDER breakpoint that overrides it, else desktop; `list_theme_globals` returns them per typography " +
5007
+ "entry under `breakpoints` (`overrides` = own values, `resolved` = effective values). Breakpoints are NOT created " +
5008
+ "by this tool. To write SEVERAL breakpoints (optionally together with desktop values) in one call, use " +
5009
+ "`breakpoints` instead of `breakpoint_id`. Requires " +
4965
5010
  "`ikas theme dev` running with the editor connected.", {
4966
5011
  project_root: z
4967
5012
  .string()
@@ -4979,18 +5024,26 @@ server.tool("update_text_style", "Update an existing theme typography token (tex
4979
5024
  .optional()
4980
5025
  .describe("true → make this the theme's default/global text style (clears any other default); false → remove the " +
4981
5026
  "default flag. Omit to leave the default state unchanged."),
5027
+ breakpoint_id: z
5028
+ .string()
5029
+ .optional()
5030
+ .describe("Existing theme breakpoint id from `list_theme_globals`. When set, the font params are written as that " +
5031
+ 'breakpoint\'s overrides (font_size/font_weight/line_height/letter_spacing only; "" removes one).'),
5032
+ breakpoints: textStyleBreakpointsParam,
4982
5033
  port: z
4983
5034
  .number()
4984
5035
  .optional()
4985
5036
  .describe("Dev server WebSocket port (default 5201)."),
4986
5037
  }, async (input) => {
4987
- const { project_root, id, name, is_default, port } = input;
5038
+ const { project_root, id, name, is_default, breakpoint_id, breakpoints, port, } = input;
4988
5039
  const args = [
4989
5040
  "update-text-style",
4990
5041
  "--id",
4991
5042
  id,
5043
+ ...(breakpoint_id !== undefined ? ["--breakpoint", breakpoint_id] : []),
4992
5044
  ...(name !== undefined ? ["--name", name] : []),
4993
5045
  ...textStyleFontArgs(input),
5046
+ ...textStyleBreakpointsArgs(breakpoints),
4994
5047
  ...(is_default === true ? ["--default"] : is_default === false ? ["--no-default"] : []),
4995
5048
  ...(port ? ["--port", String(port)] : []),
4996
5049
  ];