diff --git a/skills/generating-custom-lightning-type/SKILL.md b/skills/generating-custom-lightning-type/SKILL.md index dadbe6e..b376912 100644 --- a/skills/generating-custom-lightning-type/SKILL.md +++ b/skills/generating-custom-lightning-type/SKILL.md @@ -1,6 +1,6 @@ --- name: generating-custom-lightning-type -description: "Use this skill when users need to create Custom Lightning Types (CLTs) for Einstein Agent actions or structured input/output schemas. Trigger when users mention CLT, Custom Lightning Types, Custom Lightning Types (CLTs) with widget/mosaic/fragment rendition/renderer, JSON schemas for agents, type definitions, lightning__objectType, or editor/renderer configurations. When widget renditions are requested, you MUST first read the widget-rendition.md reference file in this skill's references/ directory and follow its complete workflow. This is complex - always use this skill for CLT work." +description: "Use this skill when users need to create Custom Lightning Types (CLTs) for Einstein Agent actions or structured input/output schemas. Trigger when users mention CLT, Custom Lightning Types, custom lightning type with widget renderer, custom lightning type with widget, CLT with widget renderer, CLT with widget, JSON schemas for agents, type definitions, lightning__objectType, or editor/renderer configurations. When a widget renderer is requested, you MUST first read the widget-rendition.md reference file in this skill's references/ directory and follow its complete workflow. This is complex - always use this skill for CLT work." metadata: version: "1.0" --- @@ -95,6 +95,10 @@ When strict validation is enabled (`unevaluatedProperties: false`), keep each pr - `lightning:tags` (metaschema restricts values; currently `flow` is the only known allowed tag) ## Generation Workflow + +> **⚠️ Widget renderer requested? STOP before step 1.** +> If the user requested a "widget renderer", "mosaic", "widget", or "fragment": you MUST read `references/widget-rendition.md` NOW before doing anything else — before drafting schema, before creating any files, before planning steps. The API context examples show `c/` renderers — those are the custom LWC pattern, NOT the widget pattern. Do not follow them for widget rendition. + 1. **Confirm the CLT approach** - If referencing Apex: capture the exact class reference (`@apexClassType/namespace__ClassName$InnerClass`). - If using standard primitives: list the fields, their Lightning primitive types, and which fields are required. @@ -130,25 +134,28 @@ When strict validation is enabled (`unevaluatedProperties: false`), keep each pr - Do not use `es_property_editors/inputList`. - Do not use `itemSchema` attributes. 4. **(Optional) Draft `renderer.json`** (only if custom UI or mosaic rendition is required) + - **⚠️ DECIDE FIRST — widget or custom LWC?** Before writing any renderer code or planning any steps: + - If the user requested "widget", "mosaic", "fragment", or "cross-platform rendering": you MUST use the **Widget renderer pattern** below. Do NOT create a custom LWC. Do NOT use the root override pattern with `c/`. Skip directly to the Widget renderer pattern now. + - Otherwise: use the root override pattern or property-level overrides described below. - **Supported shape:** Top-level `renderer` object with `renderer.componentOverrides` and `renderer.layout`. - Top-level `renderer` object. - Use `renderer.componentOverrides` for component overrides. - Use `renderer.layout` for layout. - **DEPRECATED**: Do NOT use `propertyRenderers` or `view` — these are legacy keys. Always use `componentOverrides` and `layout` instead. - - **Root override pattern** (most common for fully custom rendering UI): - - `renderer.componentOverrides["$"] = { "definition": "c/", "attributes": { ... } }` - - Use `{!$attrs.}` in attribute mappings when binding schema data to custom renderer component attributes. - - **CRITICAL**: Attribute mappings like `{!$attrs.propertyName}` must reference properties that **actually exist** in your type schema. Referencing non-existent properties will fail validation. - - **Type matching**: Attribute values must match the expected type for the component. For example, if a component expects a string attribute, passing an integer will fail validation. - - **Widget renderer pattern** (for widget rendition): - - **When to use:** Use this when users request "mosaic", "widget", "fragment", or "cross-platform rendering" for their CLT. + - **Widget renderer pattern** (for widget rendition — use when user requests "widget", "mosaic", "fragment", or "cross-platform rendering"): - **Structure:** `renderer.componentOverrides["$"] = { "type": "mosaic", "definition": "tile/mosaic", "children": [ /* UEM tree of blocks and regions */ ] }` + - **CRITICAL**: Do NOT create a custom LWC component (e.g. `c/`) for widget rendering. Widget rendition uses a `tile/mosaic` UEM block tree — not a custom component. Creating a custom LWC here is always wrong. - **REQUIRED workflow:** - - **STOP**: Do NOT attempt to create the widget renderer yourself. + - **STOP**: Do NOT attempt to build the UEM tree yourself. - **MANDATORY FIRST STEP**: You MUST fetch the reference file `references/widget-rendition.md` located in this skill's directory before proceeding. - Follow the complete workflow documented in `widget-rendition.md` using the generated CLT schema as the grounding schema. - The `widget-rendition.md` reference contains the full widget generation workflow: discovering UEM blocks via discoverUiComponents, calling getUiComponentSchemas, building the UEM tree, and writing renderer.json. - **Do not** attempt to generate widget rendition without first fetching the `widget-rendition.md` reference file. + - **Root override pattern** (for custom LWC rendering — only when widget/mosaic was NOT requested): + - `renderer.componentOverrides["$"] = { "definition": "c/", "attributes": { ... } }` + - Use `{!$attrs.}` in attribute mappings when binding schema data to custom renderer component attributes. + - **CRITICAL**: Attribute mappings like `{!$attrs.propertyName}` must reference properties that **actually exist** in your type schema. Referencing non-existent properties will fail validation. + - **Type matching**: Attribute values must match the expected type for the component. For example, if a component expects a string attribute, passing an integer will fail validation. - **Property-level override pattern**: - `renderer.componentOverrides[""] = { "definition": "es_property_editors/outputText" | "es_property_editors/outputNumber" | "es_property_editors/outputImage" | ... }`. **Valid renderer components** (examples): `es_property_editors/outputText`, `es_property_editors/outputNumber`, `es_property_editors/outputImage`. Avoid input-style components in the renderer. - **Layout pattern for renderer**: @@ -208,6 +215,7 @@ When strict validation is enabled (`unevaluatedProperties: false`), keep each pr - [ ] Bundle structure and filenames match Lightning Types requirements - [ ] Editor config uses only allowed patterns (no `es_property_editors/inputList`, no `itemSchema`); use valid components (e.g. `es_property_editors/inputText`, `es_property_editors/inputNumber`) or custom `c/` components - [ ] Renderer config uses output-style components (e.g. `es_property_editors/outputText`, `es_property_editors/outputNumber`) where applicable, not input editors +- [ ] Widget renderer uses `tile/mosaic` UEM block tree (NOT a custom `c/` LWC) - [ ] Layout configurations use `lightning/propertyLayout` with ONLY the `property` attribute (no `label`, `title`, or other attributes) - [ ] All attribute mappings (`{!$attrs.propertyName}`) reference properties that exist in the type schema - [ ] Custom LWC components have correct targets in `-meta.xml`: `lightning__AgentforceInput` for editors, `lightning__AgentforceOutput` for renderers diff --git a/skills/generating-custom-lightning-type/references/widget-rendition.md b/skills/generating-custom-lightning-type/references/widget-rendition.md index 454f12f..770d8cd 100644 --- a/skills/generating-custom-lightning-type/references/widget-rendition.md +++ b/skills/generating-custom-lightning-type/references/widget-rendition.md @@ -14,16 +14,21 @@ A widget is a UEM (Unified Experience Model) tree of blocks and regions. The wid ```ts interface BlockType { - type: 'block' - definition: string // {namespace}/{blockName} - attributes?: Record - children?: (BlockType | RegionType)[] + type: 'block' + definition: string // {namespace}/{blockName} + attributes?: Record + meta?: { + forEach?: string // expression resolving to an array, e.g. "{!$attrs.items}" + forItem?: string // loop variable (must start with $), e.g. "$item" + if?: string // boolean expression; block is omitted when falsy + } + children?: (BlockType | RegionType)[] } interface RegionType { - type: 'region' - name: string - children: BlockType[] + type: 'region' + name: string + children: BlockType[] } ``` --- @@ -88,6 +93,171 @@ interface RegionType { --- +## 🔁 Iteration with forEach / forItem + +Use `forEach`/`forItem` when a block repeats over an array. Whether that applies at the root level depends on the schema shape — see step 5 of the Workflow for the decision. + +### Rules +- Place `forEach` on the `meta` object of the **repeating** block (a row, card, list item, etc.), **not** on the container block. When a container holds repeating children (e.g. a list wrapper and its item rows), `forEach`/`forItem` goes on the child, not the container — otherwise a new container is created per item instead of one container with all items inside it. +- The value must be an expression resolving to an array: `"{!$attrs.}"`, e.g. `"{!$attrs.items}"`. +- `forItem` (required with `forEach`) names the loop variable for the current item. **Must start with `$`**, e.g. `"$item"`. +- All children of the `forEach` block reference the loop variable (e.g. `{!$item.id}`, **not** `{!$attrs.items.id}`). +- For **nested lists** (inner arrays on each item), add another `forEach` block inside the repeating block's children, using a distinct `forItem` name (e.g. `"$subItem"`). + +### Example — top-level list + +```json +{ + "type": "block", + "definition": "namespace/arrayBlockDefiniton", + "meta": { + "forEach": "{!$attrs.items}", + "forItem": "$item" + }, + "children": [ + { + "type": "block", + "definition": "namespace/blockDefinition1", + "attributes": { "content": "{!$item.id}" } + }, + { + "type": "block", + "definition": "namespace/blockDefinition2", + "attributes": { "content": "{!$item.total}" } + } + ] +} +``` + +### Example — container block with repeating child + +When a container block holds repeating items (e.g. a list wrapper and its item rows), place `forEach`/`forItem` on the **child** (the repeating element), not on the container. + +```json +{ + "type": "block", + "definition": "namespace/containerBlockDefinition", + "attributes": { "variant": "default" }, + "children": [ + { + "type": "block", + "definition": "namespace/itemBlockDefinition", + "meta": { + "forEach": "{!$attrs.items}", + "forItem": "$item" + }, + "attributes": { "title": "{!$item.name}" } + } + ] +} +``` + +### Example — nested list (inner items) + +```json +{ + "type": "block", + "definition": "namespace/arrayBlockDefiniton", + "meta": { "forEach": "{!$attrs.items}", "forItem": "$item" }, + "children": [ + { + "type": "block", + "definition": "namespace/blockDefinition1", + "attributes": { "content": "{!$item.id}" } + }, + { + "type": "block", + "definition": "namespace/blockDefinition1", + "meta": { "forEach": "{!$item.lineItems}", "forItem": "$lineItem" }, + "children": [ + { + "type": "block", + "definition": "namespace/blockDefinition2", + "attributes": { "content": "{!$lineItem.name}" } + }, + { + "type": "block", + "definition": "namespace/blockDefinition2", + "attributes": { "content": "{!$lineItem.count}" } + } + ] + } + ] +} +``` + +--- + +## 🔀 Conditional rendering with if + +- Place `"if"` on the `meta` object of a block to conditionally include it. +- The expression must resolve to a truthy/falsy value. When falsy, the block **and all its children** are excluded from the rendered output. +- `if` can be combined with `forEach` on the same `meta` object. + +### Example + +```json +{ + "type": "block", + "definition": "namespace/blockDefinition", + "meta": { "if": "{!$attrs.isTrue}" }, + "attributes": { "label": "Label"} +} +``` + +### Example — if inside a forEach loop + +```json +{ + "type": "block", + "definition": "namespace/blockDefinition1", + "meta": { "forEach": "{!$attrs.items}", "forItem": "$item" }, + "children": [ + { + "type": "block", + "definition": "namespace/blockDefinition2", + "attributes": { "content": "{!$item.id}" } + }, + { + "type": "block", + "definition": "namespace/blockDefinition3", + "meta": { "if": "{!$item.isTrue}" }, + "attributes": { "label": "Label"} + } + ] +} +``` + +### Example — forEach and if on the same block + +`forEach` and `if` can be placed together on the same `meta` object. The block is first evaluated against `if` — if falsy, the entire loop is skipped. If truthy, the block repeats for every item in the array. + +```json +{ + "type": "block", + "definition": "namespace/arrayBlockDefinition", + "meta": { + "forEach": "{!$attrs.items}", + "forItem": "$item", + "if": "{!$attrs.showItems}" + }, + "children": [ + { + "type": "block", + "definition": "namespace/blockDefinition2", + "attributes": { "content": "{!$item.id}" } + }, + { + "type": "block", + "definition": "namespace/blockDefinition3", + "attributes": { "content": "{!$item.total}" } + } + ] +} +``` + +--- + ## 💡Workflow 1. **Schema Parsing** @@ -105,7 +275,12 @@ interface RegionType { 5. **Build Widget** - Construct the UEM tree. Map each property in the **widget spec** to block properties and preserve order of the **widget spec**. -- For block properties that must show or pass runtime data, use the placeholder syntax (see **Attribute binding using placeholder syntax** above). +- **Decide whether to iterate at the root level:** + - If the schema represents a **single object** (e.g. one item — root `type: object` with scalar/list properties) — do NOT add `forEach` on the top-level block. Render its properties directly. Use `forEach`/`forItem` only on blocks that repeat over an array property within it (e.g. the list of cart items). + - If the schema represents a **collection** (e.g. a list of items) — wrap the repeating block in a `forEach` / `forItem` meta directive so the widget renders one row/card per item. See **Iteration with forEach / forItem** above. +- For block properties that must show or pass runtime data, use the placeholder syntax (see **Attribute binding using placeholder syntax** above). Inside a `forEach` block, reference the loop variable (e.g. `{!$item.attrName}`) instead of `{!$attrs.items.attrName}`. +- Add `"if"` on the `meta` object of any block that should render conditionally (see **Conditional rendering with if** above). +- For inner arrays on each item, add a nested `forEach` block with a distinct `forItem` name. - Use block properties from the schemas retrieved in step 4. 6. **Write output to CLT Bundle** @@ -121,4 +296,10 @@ interface RegionType { - When using **`execute_metadata_action`** tool, always supply **`parameters`** with the required fields above; missing `parameters` or required keys causes hard failures, not partial results. - Block definitions always follow the `{namespace}/{blockName}` convention. - Use the same definition format returned by `discoverUiComponents` when calling `getUiComponentSchemas` -- Placeholder syntax for non-list properties is `{!$attrs.}` and for list properties is `{!$attrs..item}`. \ No newline at end of file +- Placeholder syntax for non-list properties is `{!$attrs.}` and for list properties is `{!$attrs..item}`. +- **`forEach` + `forItem` are required together.** Never use one without the other. +- **`forItem` values must start with `$`** (e.g. `"$item"`, `"$order"`, `"$line"`). +- Inside a `forEach` block, children **must** reference the loop variable (`{!$item.attrName}`), not the original array path. +- Nested `forEach` blocks must use a **different** `forItem` name from the outer loop. +- `"if"` expressions must be boolean. Use comparison or logic expressions (e.g. `{!$item.isActive}`). +- `"if"` and `"forEach"` can coexist on the same `meta` object. \ No newline at end of file