← Simulator.CompanyCONTENT HISTORY

Update to Simulator.Company

Snapshot Oct 24, 2026 · 13:31 UTC · version 2.10.0

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save Simulator.Company and check future changes in Following.

Up to 10 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Simulator.Company Smart Form (CDU) STYLING specialist — authoring complex Less/CSS for Smart Forms: theme tokens, page/form/section layout, component re-skinning, reusable style patterns, responsive and design-system approaches. Use when the user wants to STYLE or RESTYLE an existing Smart Form / CDU app — change its look, build a theme, style a table / sidebar / modal / form, add a design system, fix spacing/colors/fonts, or apply a complex visual design. This skill owns the `style` / `styles/` layer; it reuses the Smart Form tools (pullSmartForm / pushSmartForm / deploySmartForm) but does NOT create form templates — for the data-schema form template use `simulator-forms`, and for page layout / viewModel / backend logic use `simulator-smart-forms` / `simulator-smart-forms-logic`. Activate on: \"style a smart form\", \"CDU styles\", \"theme the form\", \"restyle\", \"custom CSS/Less for the app\", \"style the table/sidebar/modal/button\", \"design system for the smart form\", \"make it look like …\", \"застилізувати смартформу\", \"стилі CDU\", \"тема для форми\", \"кастомний CSS\", \"оформити таблицю/ сайдбар/модалку\", \"застилизовать смартформу\", \"стили CDU\", \"тема формы\", \"кастомный CSS\", \"оформить таблицу/сайдбар/модалку\".\n",
  "included_files": [],
  "name": "simulator-styles",
  "skill_md_contents": "---\nname: simulator-styles\ndescription: >\n  Simulator.Company Smart Form (CDU) STYLING specialist — authoring complex Less/CSS for\n  Smart Forms: theme tokens, page/form/section layout, component re-skinning, reusable style\n  patterns, responsive and design-system approaches. Use when the user wants to STYLE or\n  RESTYLE an existing Smart Form / CDU app — change its look, build a theme, style a table /\n  sidebar / modal / form, add a design system, fix spacing/colors/fonts, or apply a complex\n  visual design. This skill owns the `style` / `styles/` layer; it reuses the Smart Form tools\n  (pullSmartForm / pushSmartForm / deploySmartForm) but does NOT create form templates — for the\n  data-schema form template use `simulator-forms`, and for page layout / viewModel / backend\n  logic use `simulator-smart-forms` / `simulator-smart-forms-logic`. Activate on: \"style a smart\n  form\", \"CDU styles\", \"theme the form\", \"restyle\", \"custom CSS/Less for the app\", \"style the\n  table/sidebar/modal/button\", \"design system for the smart form\", \"make it look like …\",\n  \"застилізувати смартформу\", \"стилі CDU\", \"тема для форми\", \"кастомний CSS\", \"оформити таблицю/\n  сайдбар/модалку\", \"застилизовать смартформу\", \"стили CDU\", \"тема формы\", \"кастомный CSS\",\n  \"оформить таблицу/сайдбар/модалку\".\n---\n\n# Simulator.Company Smart Form STYLING specialist\n\nYou author and apply **CSS/Less styles** for Smart Forms (CDU / Script apps) on\nSimulator.Company. Your domain is the **`style` / `styles/` layer** — themes, layout, component\nre-skinning, reusable patterns, responsive and design-system approaches.\n\nThis skill is built on patterns reverse-engineered from real production Smart Forms; the recipes\nbelow are taken from live Less, not invented.\n\n---\n\n## Scope — what this skill owns (and what it does NOT)\n\n| Concern | Skill |\n|---|---|\n| **Styling**: `style` / `styles/*`, `pages/<id>/style`, `styleClass`, themes, Less | **this skill** |\n| Form template (data fields / Account Template) | `simulator-forms` |\n| Page layout JSON (`pages/<id>/config`: grid/forms/sections/items), viewModel, locale | `simulator-smart-forms` |\n| Backend logic (Corezoid `/get` `/send`, dynamic viewModel, `changes[]`) | `simulator-smart-forms-logic` |\n| Generating a whole app (pages + middleware) from existing Corezoid processes | `simulator-app-generator` |\n\nYou **reuse** the Smart Form engine tools (`pullSmartForm`, `pushSmartForm`, `deploySmartForm`,\nfile-history/rollback) — you do not introduce new platform behaviour. When the user needs a *new\nform/page* first, defer to `simulator-smart-forms`; you come in to make it look right. To attach a\n`styleClass` to a component you may need a one-line edit to `pages/<id>/config` — that's in scope\n(it's the binding), but designing the layout itself belongs to `simulator-smart-forms`.\n\n---\n\n## How Smart Form styling works (the model)\n\n1. **One Less stylesheet, compiled per save, scoped to `.cdu-page`.** Whatever you write is wrapped\n   in `.cdu-page { … }` at serve time, so `&` = the page root and your styles can't leak out. Less\n   syntax (variables, mixins, `@import`, functions, maps, guards, `each()`) is fully supported.\n2. **File organization — and where each rule goes.** Two layouts compile identically; **prefer the\n   modular `styles/` folder** for anything non-trivial:\n   - **Legacy single file** — a root `style` file holds everything (big forms: *Admin Panel*, *CMS*, *LMS*).\n   - **Modular `styles/`** — `styles/index` is the entry point and mainly just `@import`s partials,\n     **in cascade order**:\n     ```less\n     // styles/index — the entry/manifest; imports in cascade order\n     @import \"colors_fonts\";   // tokens FIRST (everything below + page styles inherit them)\n     @import \"init_styles\";    // then platform resets + project-wide component defaults\n     // … any other shared partials (mixins, shared components) …\n     ```\n   - **Page styles** — `pages/<id>/style` is **auto-appended after** the main stylesheet (so it\n     **wins the cascade**) and **inherits all root variables/mixins** (no `@import` needed). The\n     platform saves it as `text/css` automatically. It is for **that page's exceptions only**.\n\n   | File | What belongs here |\n   |---|---|\n   | `styles/index` | Just `@import`s (the entry/manifest), in cascade order. |\n   | `colors_fonts` | Color + font **tokens** (`@color_*`, `@font_*`, `@font-face`). Define them here because everything else — including page styles — inherits them. |\n   | `init_styles` | **(a)** neutralize the platform's default styling (resets); **(b)** project-wide **component defaults** — one base look reused on every page (e.g. a `.button` skin). |\n   | `pages/<id>/style` | **Only the page's differences** from the shared styles — page layout + one-off component tweaks. Never define tokens here (they live in `colors_fonts`). |\n3. **`styleClass` is the binding contract** between layout JSON and CSS. Every grid / form /\n   section / item in `pages/<id>/config` may carry a `styleClass`; your CSS targets that class.\n   - **Static** for structure: `\"styleClass\":\"main_table\"`.\n   - **Dynamic** for backend-driven state/theme: `\"styleClass\":\"{{settings_page_text_align}}\"` —\n     Corezoid pushes the value (e.g. `text_align_right`, `active_sidebar_btn`) to switch styling.\n4. **Validation**: CSS is **not** validated on save; a Less compile error is emitted as a\n   `/* Less Error … */` comment rather than breaking the page. `styleClass` values are never\n   validated — a class with no matching rule is harmless (but dead; clean it up).\n5. **Reaching renderer internals**: the public knobs are `styleClass` + documented component\n   classes (`.button`, `.edit`, `.select`, `.table`, `.check`, …). For deeper structure use\n   substring/attribute selectors — `[class*=\"table__wrap\"]`,\n   `[data-class=\"grid-one-column\"]`.\n   **Winning the cascade** — the renderer ships base/inline styles AND per-component CSS-module rules\n   that load *after* your scoped `styles/index`, so an equal-specificity rule of yours **loses the\n   tie by source order**. Beat it by **raising specificity with more stable classes, not ids**: chain\n   your `styleClass` + an ancestor `[class*=\"…\"]` + the element — e.g.\n   `.book-table td.bc-cover-cell .file img` (0,3,2) beats the component's `.file__item__hash img`\n   (0,2,1). Prefer this class chain over `#id` (brittle — it pins the rule to a config `id`, and\n   classes are the house rule) and over a lone `!important` (it only beats non-`!important`; in an\n   `!important` vs `!important` fight specificity still decides). A doubled class (`.x.x`) is a last\n   resort. **See the verified per-component DOM map below for exact hooks (`edit`, `select`,\n   `multiselect`, `radio`, `button`, `row__<name>`).**\n\n   **The cascade math, with real numbers** (measured against a live renderer build — dump the\n   defaults yourself to confirm, see Workflow §\"dump the defaults\"):\n   - Your stylesheet is auto-wrapped in `.cdu-page`, so a **bare single-class rule you write is\n     already `0,2,0`** (`.cdu-page .myClass`) and **beats a bare renderer default `.hashed` (`0,1,0`)\n     for free** — most overrides need nothing more.\n   - **BUT the renderer theme-scopes ~⅓ of its rules** (`.theme-light .x` / `.theme-dark .x`, on\n     `#mainRoot`), and those are **also `0,2,0`** — a *tie* with your wrapped single-class rule, and\n     since defaults load **after** you, **the theme default wins**. Symptom: a `background`/`color`\n     override \"does nothing\" even though your selector clearly matches. Beat it with an extra class\n     (`0,3,0`) or `!important`.\n   - **~1 in 5 default rules already use `!important`** (heavy on `active`/`selected`/`checked`\n     states and theme colors). Matching them with your own `!important` is **normal here, not a smell**\n     — a lone `!important` still loses to a default `!important` of higher specificity, so pair it\n     with a solid class chain.\n   - **Slots own their own background/padding.** Containers like `.section__content`, the sidebar\n     slots (`.sidebar__header/__content/__footer`), and `.page__sidebar` each ship their own bg/padding\n     — painting the *parent* won't show through. Override the **slot**, not its ancestor.\n\n> Authoritative component-class + CSS reference: the **\"CSS styling\"** tag in the CDU swagger and\n> `$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-page-protocol.md`.\n\n---\n\n## Rendered DOM map (verified component internals)\n\nVerified against a live `control-cdu` render. The summary below is enough for most work; for the\n**full per-component tag tree of every component** (incl. all 4 table types, page skeleton,\noverlays) see `$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-dom-tree-reference.md`. Use this to target\nthe right element instead of guessing. **Three rules first:**\n\n1. **`styleClass` lands on the component ROOT.** E.g. `class=\"label hd-meta label__12ehI\"`,\n   `class=\"edit edit__text txt-input … bordered\"`, `class=\"radio pills … horizontal\"`. So a root\n   hook (`.hd-meta`, `.txt-input`, `.pills`) always hits — but only the **outer** element.\n2. **Internals use hashed CSS-module classes** (`label__12ehI`, `f-item-0-2-64`, `i-label-0-2-70`,\n   `clickOutside(i)-field-0-2-97`) that change between renderer builds. **Never target the hash.**\n   Reach inside via substring selectors (`[class*=\"radioItem\"]`, `[class*=\"i-icon\"]`,\n   `[class*=\"chip\"]`, `[class*=\"i-edit\"]`), stable state classes (`.checked` `.disabled`\n   `.bordered`), `data-class` attributes, or element selectors (`input`, `textarea`, `label`,\n   `svg`).\n3. **`row` / `w` grouping yields a STABLE group class `.row__<rowName>`.** An item with `\"row\":\"act\"`\n   renders inside `<div class=\"row row__act row__hash\">` whose children are\n   `<div class=\"row__item__…\" style=\"width:…%\">`. Note `w` is a **relative weight**, not a raw\n   percentage: the rendered width is `w / Σw` across the row (two items at `w:50` each get 50%; at\n   `w:50` + `w:100` they get 33% / 67%). Style the group via `.row__act` (flex container) and items\n   via `[class*=\"row__item\"]` — your reliable hook for multi-column rows, progress bars, etc.\n\n**Page skeleton & theme** (stable structural hooks):\n\n- **Scope:** all your CSS is wrapped in `.cdu-page` (`&` = page root); per-page hook\n  `.cdu-page-<pageId>`.\n- **Dark mode is a CLASS, not a media query:** `.theme-light` / `.theme-dark`. In a live render the\n  class sits on the host wrapper `#mainRoot` (an **ancestor** of `.cdu-page`), which is why\n  `.theme-dark .… {}` works from inside your scoped stylesheet. `control-cdu` itself also stamps the\n  theme class onto `#page` (the `.cdu-page` node) — so **verify the placement on your build**: if the\n  class is only on the `.cdu-page` node (not an ancestor), a wrapped `.theme-dark .foo` won't match\n  and you'd need `&.theme-dark .foo`. Prefer this class approach over `@media (prefers-color-scheme)`.\n  ⚠️ Caveat: the renderer's `light`/`dark` token maps are currently **identical** — the mechanism is\n  wired but dark mode has no distinct palette yet, so a `.theme-dark` override is the only way to make\n  dark actually differ today.\n- **Grid regions:** `[data-class=\"grid-one-column\"]` / `[data-class=\"grid-two-column\"]`\n  (+`-left`/`-right`); header/footer regions via `[class*=\"gridtwo__header\"]` /\n  `[class*=\"gridtwo__footer\"]`.\n- **Section slots:** `[data-class=\"section\"]` (+ `[class*=\"block__\"]` for `type:\"block\"` cards);\n  inner `[class*=\"section__header\"]` / `[class*=\"section__content\"]`.\n- **Toasts:** two `[class*=\"notify\"]` containers (top inside `#page`, bottom at `#mainRoot` end).\n\n**Per-component root + key inner hooks** (`<sc>` = your `styleClass`):\n\n| Component | Root selector (styleClass here) | Key inner hooks / notes |\n|---|---|---|\n| `label` | `.label` / `[data-class=\"label\"]` | `<span>` text; BBCode → real tags; `align`→ `.left__`/`.center__`/`.right__` |\n| `divider` | `.divider` | empty |\n| `edit` (all types) | `.edit` (+ `.edit__<type>`) | `.field > input` / `textarea`; states `bordered`(box)/`selected`/`error`; kill box `.field{border:none}`; help/err `[class*=\"Component-helperText\"]` |\n| `select` | `.select` (outer) | readonly `<input>` in `[class*=\"i-edit\"]` + caret `[class*=\"endAdornment\"]`; **no native `<select>`** |\n| `multiselect` | `.multiselect` | chip field: `[class*=\"chip\"]` + search `<input>` via `[class*=\"clickOutside(i)-field\"]`; **not** checkbox rows |\n| `radio` | `.radio` (+ `.horizontal` for row) | options `[class*=\"radioItem\"]`(+`.checked`/`.disabled`); hide svg `[class*=\"i-icon\"]`, style label `[class*=\"i-label\"]` → pills/scales |\n| `check` | `.check` | `[class*=\"f-icon\"]` svg + `<label>`; states `.checked`/`.error`; native `input{appearance:none}` |\n| `toggle` | `.toggle` (+ `.left__`/`.right__`) | `[class*=\"toggle__button\"]`(+`.active`) / `[class*=\"i-switch\"]` |\n| `slider` | `.slider` (+ `.skillBar__`) | `rc-slider`: `.rc-slider-rail`/`-track`/`-handle`/`-dot`; `[class*=\"slider__header\"]`, `[class*=\"slider__min\"]`/`max` |\n| `otp` | `.otp` | boxes `[class*=\"otp__edit\"] input` |\n| `phone` | `[data-class=\"phone\"]` | `#countryCode .select` + `#number .edit input`; `[class*=\"phone__items\"]` |\n| `image` | `[data-class=\"image\"]` | `<img>` (src **proxied** via `/api/1.0/image`); `align`→ `.center__` |\n| `timer` | `[data-class=\"timer\"]` | `<span>` text |\n| `comments` | `[data-class=\"comments\"]` | `[class*=\"mes__wrap\"]`, `[class*=\"mes__name\"]`, `[class*=\"mes__content\"]`, avatar `[class*=\"i-avatar\"]` |\n| `carousel` | `.carousel` | preview pane `[class*=\"carousel__preview\"]` (zoom/nav btns), thumb strip `[class*=\"carousel__content__item\"]`(+`.active__`); give it a full File `value` for a correct preview (tolerates a missing `type` — won't error-stub) |\n| `button` | wrapper `[data-wrapper-for=\"<id>\"]` → inner `#<id>.button.button__<type>` | **`<sc>` is on the INNER button**; text `[class*=\"button__label\"]`; align via wrapper/`.row__<name>`; 7 types via `.button__<type>` |\n| `copy` | `[data-class=\"copy\"]` | `[class*=\"copy__container\"]`, icon `[class*=\"i-icon\"]`, label `[class*=\"i-label\"]` |\n| `tab` | `.tab` (outer `.tab__…`) | items `[class*=\"tab__item\"]`(+`.active`/`.error`); `hidden` option is absent from DOM |\n| `stepper` | `[data-class=\"stepper\"]` | items `[class*=\"stepperItem\"]`(+`.completed__`/`.active__`); label `[class*=\"stepperItem__label\"]` |\n| `mainMenu` | `nav.mainMenu` | items `[class*=\"mainMenuItem\"]`(+`.active`); groups `<details>`/`[class*=\"mainMenuItemGroup\"]`; `data-depth` + `--depth`; badge `[class*=\"i-badge\"]` |\n| `upload` | `[data-class=\"upload\"]` | `[class*=\"upload__box\"]`, corners `[class*=\"upload__corner\"]`, `input[type=file]` (webcam: trigger `div.upload__file__…[role=button]`, needs `extra.accept`) |\n| `file` | `.file` | image → `[class*=\"file__item\"] img`; pdf/doc → `.pg-viewer`/`.pdf-viewer`; path chosen by mime `value.type` |\n| `attachment` | `[data-class=\"attachment\"]` | upload ctrl `[class*=\"i-upload\"]`; chips `[class*=\"fileItemChip\"]` (text `[class*=\"e-chipText\"]`, remove `[class*=\"e-delFileIcon\"]`) |\n| `signature` | `.signature__wrap` | `<canvas>`; toolbar `[class*=\"signature__toolbar__clear\"]`/`__save` |\n| `table` | `[data-class=\"table\"]` (+ `.table__check`/`__radio`/`__group`) | see **Tables** below |\n| `widget` | `[data-class=\"widget\"]` | `iframe[class*=\"iframe__\"]`; `[class*=\"widget__inner\"]`(+`.hidden__` until load) |\n| row/w group | `.row__<rowName>` | flex container; items `[class*=\"row__item\"]` (inline `width`) |\n| `draggable` (sortable/contentLoop) | `[data-class=\"draggable\"]` | grip `[class*=\"draggable__handle\"]`, body `[class*=\"draggable__content\"]` |\n| `notification` (toast) | **none** — page root `[class*=\"notify\"]` | `[class*=\"notifyItem\"]` (+ severity `[class*=\"success\"]`/`[class*=\"error\"]`/`[class*=\"info\"]`) → text `span[class*=\"i-title\"]` + close `i[class*=\"closeIcon\"] > svg .fill` |\n\n**Tables** (DOM is a real `<table>`): head cells `[class*=\"table__head\"]` (sortable col\n`[class*=\"sortable__\"]`, sort arrow `[class*=\"table__head__icon\"]`); sticky col → `td.sticky-col`\n+ the head cell's own `styleClass`; body rows `.table-row` / `[class*=\"table__row__\"]`\n(selected radio row → `.active__…`); first column for `check`/`radio` → `[class*=\"table__check\"]`\n/ `.i-radioItem-…`; group title row → `[class*=\"table__row__group\"]`. **Default-table cell\nmini-components** inside `<td.table-cell>`: file → `[class*=\"table__img\"]`, copy →\n`#<id>--copy[data-class=\"copy\"]`, check → `#<id>--check[class*=\"table__check\"]`, button →\n`[class*=\"table__button\"]` (id `<id>--button`, **not** `.button`).\n\n> **Table cells are NOT text-only.** A `default`-table cell can be plain text, an **image** (`file`\n> cell → real `<img>`), a **button**, a `copy`, or a `check` — so covers/actions live inside a table\n> (cell JSON shape belongs to `simulator-smart-forms`; you style via each cell's own `styleClass`,\n> which lands on its `<td>`). And **plain + head cells render BBCode** (verified — see the BBCode\n> matrix below): the text goes into a plain `<td>`/`<div>`, so `[b]/[color]/[div]…` expand there.\n> This is why you can build a status pill in a cell **without** a button cell: put `[div]Label[/div]`\n> in the plain cell and style `.<cellClass> div`.\n\n**Overlay sections:** `modal` → backdrop `[class*=\"i-bg\"]` > box `[class*=\"modal__\"]` (+ size\n`i-small`/`i-medium`/`i-large`/`i-xlarge` ← `modalSize`), header `[class*=\"section__modal__header\"]`.\n`float` → `[class*=\"float__\"]` inside `[class*=\"Component-wrapper\"]` with drag bar\n`[class*=\"section__header__dragable\"]` + 8 `[class*=\"Component-resizeHandle\"]`.\n\n> ⚠️ **File-bearing components need a complete `value`.** The **`file` component and a `default`\n> table's `file` cell** read mime via `value.type` **with no guard** — a File missing `type` throws\n> and the item renders as a `[class*=\"item__error\"]` stub. (`carousel` and `attachment` also read\n> `value.type` but tolerate it missing — `carousel` defaults to `''` and uses optional chaining — so\n> they won't stub out; still give them the full shape for a correct preview.) Give File values the\n> full shape `{fileName, fileSrc, title, type, size}`. A stub can also **suppress later siblings** (a\n> crashing table cell hid the `modal`/`float` sections after it). `upload[webcam]` needs\n> `extra.accept`. With complete values everything renders statically — no backend needed. Full\n> per-component trees: `$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-dom-tree-reference.md`.\n\n**Worked example — radio rendered as pills** (the native input is hidden; the svg is the control):\n\n```less\n.pills { display:flex; flex-wrap:wrap; gap:8px; }\n.pills [class*=\"i-icon\"] { display:none; }                 // hide the svg circle\n.pills [class*=\"radioItem\"] { border:1px solid @line; border-radius:100px; }\n.pills [class*=\"radioItem\"] label { padding:7px 16px; }    // make the whole pill the label\n.pills [class*=\"radioItem\"].checked { background:@ink; }\n.pills [class*=\"radioItem\"].checked label { color:#fff; }\n```\n\n**Worked example — toast notification.** Toasts render at the page root (inside `.cdu-page`, which\nscopes your CSS), so you reach them structurally — there is no `styleClass`. For the DOM nesting +\nthe two traps (`[class*=\"i-icon\"]` matches the close ✕, not a severity icon — don't blanket-hide it;\n`[class*=\"i-title\"]` also matches the wrapper `i-titleContainer` — qualify as `span[class*=\"i-title\"]`)\nsee the **Toasts** entry in `cdu-dom-tree-reference.md`. A reusable skin:\n\n```less\n[class*=\"notify\"] [class*=\"notifyItem\"] {\n  background:@white; color:@ink; border:1px solid @line; border-radius:@r;\n  box-shadow:0 8px 28px rgba(0,0,0,.12); padding:14px 16px;\n  display:flex; align-items:center; gap:10px;\n}\n[class*=\"notify\"] [class*=\"notifyItem\"]::before {        // severity dot\n  content:\"\"; width:8px; height:8px; border-radius:50%; background:@ink-3; flex-shrink:0;\n}\n[class*=\"notify\"] [class*=\"notifyItem\"][class*=\"success\"]::before { background:#16a34a; }\n[class*=\"notify\"] [class*=\"notifyItem\"][class*=\"error\"]::before   { background:#dc2626; }\n[class*=\"notify\"] [class*=\"titleContainer\"] { flex:1; min-width:0; }\n[class*=\"notify\"] span[class*=\"i-title\"] { font-size:14px; font-weight:500; color:inherit; }\n[class*=\"notify\"] [class*=\"closeIcon\"] { opacity:.45; cursor:pointer; }   // keep the ✕\n[class*=\"notify\"] [class*=\"closeIcon\"]:hover { opacity:1; }\n[class*=\"notify\"] [class*=\"closeIcon\"] svg .fill { fill:currentColor; }\n```\n\n> Hashes (`-0-2-64`) are version-pinned to the renderer build — substring selectors survive a bump,\n> exact-hash selectors don't. If a restyle suddenly breaks, re-dump the rendered DOM (browser\n> DevTools → copy `outerHTML` of `#page`) and re-verify these hooks.\n\n---\n\n## BBCode — where it renders (empirically verified)\n\nA component's text is expanded to HTML by the client (`Utils.bbCodeToHtml`) **only in some fields**.\nThe smart-form BBCode set (swagger `bbcode` tag) is\n`[b] [i] [u] [ul][*] [iurl=…] [url=…] [div] [br] [color=…] [size=…] [bg=…]` — note `[div]` (the only\nblock-level tag) and `[iurl]` (internal link); there is **no `[span style=…]`** here (that exists\nonly in actor/reaction BBCode). Mapping: `[color]→<span style=\"color\">`,\n`[bg]→<span style=\"background-color\">`, `[div]→<div>`.\n\n**Verified support matrix** (built by rendering a live probe string, not read off the swagger):\n\n| Renders BBCode ✅ | Stays literal ❌ |\n|---|---|\n| `label.value` | `edit` value / placeholder / multiline |\n| `button.title` | `select` value · `multiselect` option |\n| `check.title` | `radio` option title |\n| `stepper` option title | `toggle.title` · `tab` option title |\n| `form.title`, `mainMenu` title, `comments`, `carousel` item title, `upload.title` | `copy.title` |\n| **`table` head title AND plain cell** | |\n\nHeuristic: it expands where the text lands in a plain **text node** (`<span>/<div>/<td>`) and stays\nliteral where it lands in a form-control **`value`/`placeholder`** — but it's only a rough heuristic:\nit's whether that specific component passes its title through `bbCodeToHtml`/`dangerouslySetInnerHTML`,\nnot the node type. `check` ✅ but `radio` ❌ even though both render a `<label>`; `stepper` ✅ but\n`toggle`/`tab` ❌. **Trust the matrix (built from the renderer source), not the swagger:** the swagger\nmarks only `label/button/mainMenu` as bbcode fields, yet table cells clearly render it. When a field\nisn't in the matrix, confirm it in the live UI before relying on it.\n\n**Styling use — inject a styleable element where there is none.** A plain `table` cell is bare text\nin a `<td>` (no inner wrapper to hook). `[div]…[/div]` gives you an inner `<div>` to style as a\npill/badge (`.<cellClass> div { … }`); `[bg]`/`[color]` give inline `<span>`s. (Alternative with no\ninner element: absolutely-position the `<td>` so it shrink-wraps its text into a pill.)\n\n---\n\n## Workflow (reuses the Smart Form tools)\n\n```\n1. pullSmartForm(actorId)        → downloads <actorId>/develop/ + production/ (incl. style / styles/, pages/<id>/style)\n2. Edit ONLY under develop/:\n     - styles/index (+ partials)  OR  the root `style` file\n     - pages/<id>/style           (page-specific)\n     - pages/<id>/config          (only to add/adjust a styleClass hook)\n3. pushSmartForm(actorId)        → validates + uploads changed files; style/styles files carry MIME text/css (the push tool sets it; the server stores the client-supplied type — it does not force it by folder)\n4. deploySmartForm(actorId)      → publishes develop → production (when approved)\n```\n\nRules: **edit `develop` only** (`production` is readonly); **`pullSmartForm` first** (it writes the\n`.manifest.json` push needs). Files under `styles/` are sent as `text/css`; everything else\n`application/json`.\n\n**Verifying a styling result.** `appGetPage` returns the **server-resolved config** (locale/viewModel\nexpanded) — it does **not** run CSS compilation or `bbCodeToHtml`, so you **cannot** confirm a visual\nresult or whether BBCode expanded from it. Visual/BBCode results are verified in the live UI by the\nuser.\n\n**But you CAN and SHOULD confirm the Less compiles — locally, before pushing.** This is the one\nstyling failure that is otherwise completely silent: a compile error is not raised at serve time, it\nis emitted as a `/* Less Error … */` comment and the page renders **unstyled**. `pushSmartForm` does\nnot compile CSS and `appGetPage` never returns it, so nothing in the toolchain reports it.\n\nTwo details make the naive command fail, so use this recipe as-is (verified against a real 6-file\napp):\n\n- Smart Form partials are stored **without a `.less` extension**, and Less resolves\n  `@import \"colors_fonts\"` as `colors_fonts.less` — so copy them to `*.less` first.\n- `lessc` cannot read a process-substitution path (`<(…)` → `EBADF: bad file descriptor`), so write\n  a real temp file.\n- The npm package is **`less`** (the binary is `lessc`); a package literally named `lessc` also\n  exists on npm and is *not* the compiler — always pass `--package=less`.\n\n```bash\ncd <actorId>/develop\nrm -rf .lesscheck && mkdir .lesscheck\nfor f in styles/*; do cp \"$f\" \".lesscheck/$(basename \"$f\").less\"; done\n\n# the main sheet, wrapped exactly as the server wraps it (this is what makes `&` the page root)\n{ echo '.cdu-page {'; sed 's/@import \"\\(.*\\)\";/@import \"\\1.less\";/' styles/index; echo '}'; } \\\n  > .lesscheck/main.less\nnpx --yes --package=less lessc .lesscheck/main.less /dev/null && echo \"OK styles/index\"\n\n# each page sheet, with the tokens prepended so its variables resolve\nfor p in pages/*/style; do\n  { echo '.cdu-page {'; echo '@import \"colors_fonts.less\";'; echo '@import \"init_styles.less\";';\n    cat \"$p\"; echo '}'; } > .lesscheck/page.less\n  npx --yes --package=less lessc .lesscheck/page.less /dev/null && echo \"OK $p\"\ndone\nrm -rf .lesscheck\n```\n\nVerified while doing this: `@font-face` and `@media` blocks correctly bubble **out** of the\n`.cdu-page { … }` wrapper, so keeping them in an imported partial is safe.\n\n**When to ask for the rendered HTML.** First fix from the DOM map below; if it doesn't land, correct\nonce more. **If after the second correction the user still doesn't get the expected result, stop\nguessing and ask them to copy the element's HTML** (Inspect → Copy `outerHTML` of the element, or dump\n`#page`). The real DOM — hashed classes, wrapper nesting, inline sizes — resolves what the map can't;\nmost restyle stalls end the moment you read it.\n\n---\n\n## Starter kit — snippets that recur across real forms\n\nThese appear near-verbatim in multiple production forms; lift them as a baseline.\n\n**`colors_fonts` — tokens + font (define here; everything, incl. page styles, inherits them).**\n```less\n// color tokens (flat is fine for small/medium forms; mature toward Less maps for large apps)\n@color_primary:#151f6d; @color_white:#fff; @color_black:#0f0f0f; @color_border:#EAECF0;\n@color_grey:#b0adb7; @font_main:'Inter';\n// status palette as one token per state (drives chips/rows by status)\n@color_new:#A7D8F0; @color_in_progress:#A7E3B1; @color_completed:#B6E3C5; @color_pending:#FBE7A1;\n// icons/logos as URL tokens (proxied workspace assets or relative attachments/…)\n@icon_search: url('https://…/api/1.0/download/…svg?preview=true');\n@font-face { font-family:'Inter'; font-weight:100 900; font-display:swap;\n  src:url(https://fonts.gstatic.com/…woff2) format('woff2'); unicode-range:U+0000-00FF, …; }\n```\n\n**`init_styles` — platform resets + project-wide component defaults.** `@import`ed from\n`styles/index` *after* `colors_fonts`. Page-specific exceptions do **not** go here (→ `pages/<id>/style`).\n```less\n// ── 1. core resets ───────────────────────────────────────────────────────────\n*, *::before, *::after { box-sizing: border-box; }            // the single key reset\n// Every .section__content ships its OWN grey bg + padding:20px 16px 0 + margin-bottom:20px\n// (some rules theme-scoped → 0,2,0), so it paints a grey padded box INSIDE your cards.\n// Neutralize it once here; re-add padding on your own card wrapper. !important beats the theme rule.\n.section .section__content { background:transparent !important; margin-bottom:0 !important; padding:0 !important; }\n.label, .button, .form { margin:0 !important; font-size:16px; line-height:1.5; }\n[class*=\"row__item\"] { display: contents; }                   // row/w items flow into the parent\n.button-wrapper { width:auto !important; display:block; }\n.button-wrapper:has(.hidden) { display:none; }\n\n// ── 2. platform chrome (header/footer) — NOT hidden by default; reachable via:\n//   & { #pageWrap > [class*=\"content\"] > [class*=\"header\"],\n//       #pageWrap > [class*=\"content\"] > [class*=\"footer\"] { display:none !important; } }\n\n// ── 3. (optional) full-bleed — drop the default centered/capped card ─────────\n.content__main { padding:0 !important;\n  [data-class=\"grid\"] { display:block; }\n  [data-class=\"grid-one-column\"] { max-width:none; } }\n\n// ── 4. project font (token from colors_fonts) ────────────────────────────────\ninput, textarea, .button > span, .toggle__title,\n:not(&) body > .popoverContent, span[class*=\"label\"], & { font-family:@font_main, sans-serif; }\n\n// ── 5. utilities ──────────────────────────────────────────────────────────────\n.visually_hidden { position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;\n  white-space:nowrap; clip-path:inset(100%); clip:rect(0 0 0 0); overflow:hidden; }\n.font_12{font-size:12px;} .font_14{font-size:14px;} .mb_10{margin-bottom:10px !important;}\n\n// ── 6. loading overlays (platform spinners) ──────────────────────────────────\n[class*=\"button__spinner__wrap\"], [class^=\"table__spinner__wrap\"] {\n  position:fixed; inset:0; z-index:10000; display:flex; align-items:center;\n  justify-content:center; background:rgba(0,0,0,.1); backdrop-filter:blur(1px); }\n\n// ── 7. project-wide component defaults (customize to brand) ──────────────────\n.button {                                   // ONE base button look used on every page\n  padding:14px 20px; border:none; border-radius:10px; font-size:15px; font-weight:600;\n  cursor:pointer; line-height:1; display:flex; align-items:center; justify-content:center; gap:8px;\n  transition:all .3s cubic-bezier(.4,0,.2,1);\n  &.primary_btn { background:@color_primary; color:@color_white;\n    &:hover { transform:scale(1.02); box-shadow:0 6px 20px fade(@color_primary,40%); } }\n  // … add your semantic variants (.submit_btn, .cancel_btn, …) by this pattern …\n}\n.modal {                                    // default sizing by modalSize\n  &[class*=\"small\"]  { width:400px !important; }\n  &[class*=\"medium\"], &[class*=\"large\"] { width:auto !important; max-height:90% !important; }\n  &[class*=\"xlarge\"] { width:80% !important; max-height:90% !important; }\n}\n```\n\n---\n\n## Pattern catalogue (reusable recipes)\n\n### Layout\n- **Centered card form** — cap and center the grid wrapper:\n  `[data-class=\"grid-one-column\"] { max-width:440px; margin:20px auto; padding:20px; background:#fff; border-radius:8px; box-shadow:0 0 10px #0000001a; }`\n- **Full-bleed admin** — unlock the cap: `.content__main [data-class=\"grid-one-column\"] { max-width:none; }`\n- **Fixed sidebar shell** — `.sidebar { width:250px; grid-template-rows:80px 1fr 120px; }` with header/content/footer panes.\n- **Sidebar `styleClass` + `mainMenu` indent trap** — the sidebar rail **ignores `grid.sideBar.styleClass`**; style it via the base slots `.sidebar` / `.sidebar__header|__content|__footer` / `.page__sidebar` (layout — fixed width, pinned footer — as in *Fixed sidebar shell* above). For a `mainMenu` inside it, **do not** target `[class*=\"mainMenuItem\"]` — the substring also matches the branch wrappers (`.mainMenuItemGroup`, `__summary`, `__dropdown`), so `padding` accumulates per level into huge gaps. Zero the wrappers, style only the exact `.mainMenuItem` (the clickable row), and indent by level with the renderer's `--depth` var:\n  ```less\n  nav.mainMenu [class*=\"mainMenuItemGroup\"], nav.mainMenu details, nav.mainMenu summary { margin:0 !important; padding:0 !important; }\n  nav.mainMenu .mainMenuItem { padding:7px 10px; padding-left:calc(10px + var(--depth,0) * 14px); }\n  ```\n- **Pinned footer + scrolling body** — `.info_form{height:calc(100vh - 50px); display:flex; flex-direction:column;} .content_section{flex:1; max-height:calc(100vh - 100px);}`\n- **Card-per-iteration wrapper** — a bare `contentLoop` renders its iterations flat, with no per-iteration box. Set the section's `sortable:true` and the renderer wraps **each iteration** in a `.draggable` card with a drag handle; hide the handle to drop the drag affordance and keep just the wrapper (hiding `.draggable__handle` fully disables dragging — it is the only activator — so there is no `reorder` submit to handle):\n  ```less\n  .section__content .draggable__handle { display:none !important; }          /* kill grip → drag off */\n  .section__content .draggable { border:none !important; background:transparent !important;\n    padding:0 !important; box-shadow:none !important; cursor:default; }       /* reset outer card, avoid double frame */\n  .section__content .draggable__content { border:1px solid #ececec; border-radius:10px; padding:14px; }\n  ```\n- **Give a `row` its own class (multi-token `row`)** — `styleClass` on a `row` is dropped, but the `row` value is space-separated: the first token is the row id (→ `.row__<id>`), and every **extra token the renderer adds as a literal class** on the row wrapper. So `row:\"1 my_row\"` and `row:\"2 my_row\"` both get `.my_row` — a shared, stable hook to style many rows at once (the hashed `.row__…` is per-build and unreliable).\n\n### Components — re-skin, don't accept defaults\n- **Floating-label input** — `.edit__text .label { top:36px; } .edit__text.selected .label { top:16px; font-size:12px; } .edit__text.focus .field { border-color:@blue; box-shadow:0 0 0 2px #007bff40; }`\n- **Custom checkbox** — hide native (`input{-webkit-appearance:none}`, `i{display:none}`), draw tick on `.checked input::after` (rotated border).\n- **Card data table** — `.main_table { border-radius:12px; border:1px solid @border; } .main_table [class*=\"table__wrap\"]{ overflow:hidden; border-radius:12px 12px 0 0; }` + custom pagination by swapping `[id*=\"table-next\"] i::after { content:\"\"; background-image:@icon_arrow_right; }`. Use **modifier classes**: `.main_table.thin_table`, `.no_thead`, `.center_cell`.\n- **Table/radio → cards** (the e-commerce look): `thead{display:none}`, `tbody{display:flex;flex-direction:column;gap:10px}`, `.table-row{border:1px solid #ddd;border-radius:8px;padding:10px}`, highlight selection with `.table-row:has([class*=\"radioItem\"].checked){border-color:#34303d}`.\n- **Table pagination at the bottom, centered** — a `table type:\"group\"`'s pagination is a **sibling** of `[class*=\"table__wrap\"]` (not part of the rows) and defaults to `position:absolute` (pinned top, overlapping content). Lay the table root as a column and re-place it:\n  ```less\n  .myTable { display:flex; flex-direction:column; }\n  .myTable [class*=\"table__wrap\"] { max-height:none !important; height:auto !important; }\n  .myTable [class*=\"table__pagination\"] { position:static !important; order:99;\n    display:flex; justify-content:center; gap:8px; margin-top:20px; }\n  ```\n- **Status chip** — state-class + token map: `.status_cell.completed-… div{ background:@color_completed; }` (backend sets `styleClass:\"status_cell completed-…\"`).\n- **Button variants** — base `.button` + semantic modifiers (`.blue_btn`, `.red_button`, `.submit_btn`); hover-lift (`transform:scale(1.02); box-shadow:…`) or an animated `::after` fill-sweep (`transform:skewX(-20deg)`).\n- **Toast notifications** — centered, severity icon via `[class*=\"notifyItem\"][class*=\"success\"]::before{ background-image:@icon_success; }`.\n- **Modal sizing** — base `.modal` + `:has(.<innerSection>)` to size each, or size-class match `&[class*=\"xlarge\"]{ width:80% !important; }`.\n- **Breadcrumbs (via `tab`)** — model a crumb trail as a `tab` (`options[]` = crumbs, each `{value,title}`; `value` = current; `submitOnChange:true` so a click posts the value). Two `tab` traps surface only once you restyle it: the label `[class*=\"i-label\"]` defaults to `width:0; overflow:hidden` (text clips to nothing), and in a fixed tab (`i-fixed`) each `[class*=\"tab__item\"]` collapses to `width:0` (options overlap). Fix both with `width:auto`:\n  ```less\n  .breadcrumbs [class*=\"tab__item\"] { width:auto !important; min-width:max-content !important;\n    flex:0 0 auto !important; margin:0 !important; }\n  .breadcrumbs [class*=\"tab__item\"] [class*=\"i-label\"] { width:auto !important; overflow:visible !important; }\n  .breadcrumbs [class*=\"tab__item\"]:not(:last-child)::after { content:\"›\"; margin:0 10px; opacity:.6; }\n  ```\n\n### Interactivity (CSS-only, no backend round-trip)\n- **Accordion / dropdown / collapsible tree** — a `check`/`toggle` with `.checked`, expanded by parent `:has()`, animated via `grid-template-rows:0fr → 1fr`:\n  ```less\n  .menu { display:grid; grid-template-rows:0fr; overflow:hidden; transition:grid-template-rows .32s; }\n  .sidebar:has(.nav_btn.checked) .menu { grid-template-rows:1fr; }\n  ```\n  When the toggle and its target are **siblings in the same `.section__content`** (the common case), prefer the sibling combinator — no `:has()` needed: `.myCheck.checked ~ .myCollapsible { max-height:300px; }`. `.checked` lands on the component's outer wrapper. Reserve `:has()` for non-sibling targets.\n- **Slide-in panel / popover** — toggle `opacity`/`transform`/`pointer-events` under `&:has(.trigger.checked)`.\n\n### State without CSS-only tricks\n- **Screens-as-sections** — put each step/success/error/loading state as a sibling **section**, the backend flips `visibility` (one stylesheet, many screens).\n- **Hidden state carriers** — `edit` items with `styleClass:\"visually_hidden\"` stash client state (selected id, cart id) for submit.\n\n### Icons\n- **Recolorable monochrome** — `mask:url(@icon) no-repeat center; mask-size:contain; background-color:currentColor;` (inherits text color). Bundled asset: relative `attachments/cart.svg`; external: proxied `/api/1.0/download|image`.\n- **Full-color** — `background-image:@icon` token.\n\n### RTL / theme toggle from the backend\n- Page root `styleClass:\"{{settings_page_text_align}}\"`; rule `.form.text_align_right { … flex-direction:row-reverse; text-align:right; … }`. The backend pushes one value to mirror the whole UI. Same mechanism toggles nav-active state, light/dark, etc.\n\n---\n\n## Design-system approach (advanced — for large/branded apps)\n\nWhen a form grows or needs strict brand consistency, build a **token system in Less** (this is the\n*LMS* form's approach — Tailwind/Untitled-UI in Less):\n\n```less\n// maps for everything, not just color\n@breakpoint: { minimum-mobile:375px; mobile-tablet:820px; tablet-desktop:1180px; }\n@space:      { 0:2px; 1:4px; 2:8px; 3:12px; 4:16px; 5:20px; 6:24px; 8:32px; /* … */ 64:256px; }\n@color: { @gray:{25:#FCFCFD; /* … */ 700:#344054; 900:#101828;} @brand:{…} @error:{…} @success:{…} }\n\n// generate utility classes from a map (each + guard)\n.make-space-set(@base,@rule,@min,@max){ each(@space,{ .apply(@k,@v);\n  .apply(@k,@v) when (@k<=@max) and (@k>=@min){ .@{base}-@{k}.@{base}-@{k}{ @{rule}:@v; } } }); }\n.make-space-set(mt, margin-top, 0, 16);   // → .mt-0 … .mt-16, applied in styleClass\n\n// typography scale via lookup mixin\n.text-font(@size; @weight:regular){ @s:@typography[@text][@@size];\n  font-size:@s[font-size]; font-weight:@font-weight[@@weight]; line-height:@s[line-height]; }\n\n// responsive tokens: Less map → CSS var overridden per breakpoint\n@media (min-width:@breakpoint[tablet-desktop]) { :root { --space-large: 32px; } }\n.gap-large { gap: var(--space-large); }\n```\n\nAdvanced Less available: `each()`, guards (`when`), maps + indirect lookup (`@@key`),\n`replace()`, `extract()/if()/isnumber()/isdefined()`, detached-ruleset returns (`mixin()[@result]`).\n**Caveat:** keep this in a **modular `styles/`** split (`colors_fonts`, `space`, `typography`,\n`shadows`, `mixins`, `index`) — a single 7k-line file is the maintainability ceiling.\n\n---\n\n## Gotchas (learned from real forms)\n\n- **Windows filenames**: a folder/file titled with a reserved char (`\\ / : * ? \" < > |`) breaks\n  `pullSmartForm` on Windows (`mkdir` fails). Don't name definitions like `edit|create_…`. If a\n  pull aborts on `mkdir … syntax is incorrect`, rename the offending folder in the editor and\n  re-pull (or recover content from a `.graph` export that carries `scripts/<uuid>/…` file trees).\n- **Winning the cascade**: component CSS-modules load after you and win equal-specificity ties — beat\n  them with a chain of stable classes (`styleClass` + `[class*=\"…\"]` + tag), not `#id` or a lone\n  `!important`. Full worked example + cascade math in §\"How Smart Form styling works\" pt 5.\n- **Never write `.cdu-page` yourself**: the compiler wraps your whole stylesheet in `.cdu-page { … }`\n  at serve-time, so a hand-written `.cdu-page` (or `.cdu-page:has(…)`) becomes a *descendant*\n  (`.cdu-page .cdu-page…`) and silently never matches. The page root is `&` (= `.cdu-page`): paint the\n  page with `& { background:#e30613; min-height:100vh; }` and target it with `&:has(…)`.\n- **Image/`file` cell renders \"wide but a thin strip\"**: not a width bug but a **clipping ancestor** —\n  a wrapper (`.file` / `.file__item`) keeps a small fixed height + `overflow:hidden`. Force the box at\n  **every** wrapper level (`height` + `min/max-height` + `width:100%` + `max-width:none` +\n  `overflow`), not only on `<img>`. The default clamp is `max-height:32px` on both `<img>` and\n  `[class*=\"table__img\"]`, and `max-height` always beats `height` — so `max-height:none` is the key.\n  One height knob down the whole chain (`.card-img` = the cell's `styleClass`, applied on `<td>`):\n  ```less\n  .card-img, .card-img [class*=\"table__img\"], .card-img .file, .card-img [class*=\"file__item\"] {\n    width:100%; height:240px !important; max-height:none !important; display:block; }\n  .card-img img { width:100% !important; height:100% !important; max-width:none !important;\n    max-height:none !important; object-fit:contain; }   /* contain = whole image; cover = fill + crop */\n  ```\n- **Dead `styleClass`**: placeholder classes (`my-custom-class`) with no rule are harmless but\n  noise — remove them.\n- **Stale copies**: forms accumulate `style(copy)` / `style BACKUP` files — they're not wired in;\n  don't analyze or ship them.\n- **External images are proxied**: `https://…` URLs in CSS get `/api/1.0/image?src=` prepended\n  automatically (actor-attached images are internal and not proxied).\n- **`//` comments** compile away (prefer over `/* */`); keep nesting **< 3** levels.\n- **Edit `develop` only**; `pages/<id>/style` needs no `@import` (auto-included).\n\n---\n\n## Reference documents\n\n| Path | When to read |\n|---|---|\n| `$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-dom-tree-reference.md` | **The rendered-DOM map**: full per-component tag tree + class names, stable hooks vs. volatile hashes, page skeleton & theme classes, all table types, and which components need backend data to render |\n| `$CLAUDE_PLUGIN_ROOT/docs/user-flows/cdu-page-protocol.md` | Component class catalogue, `styleClass`, templating, change protocol |\n| `$CLAUDE_PLUGIN_ROOT/docs/user-flows/smart-forms.md` | Project file structure, deploy/release, styles compilation |\n| `$CLAUDE_PLUGIN_ROOT/skills/simulator-smart-forms/SKILL.md` | The pull/push/deploy cycle and page-config format this skill builds on |\n| CDU swagger → **\"CSS styling\"** tag | Authoritative Less guide: organization, component default classes, best practices, image proxying |\n"
}

SHA-256 of public snapshot: e279fd465eb097e298f0f9bbd8020630d9017ebf6aef50c3651ae3599d20fc22