diff --git a/.gitignore b/.gitignore index 7e40e99..5cca3e9 100644 --- a/.gitignore +++ b/.gitignore @@ -245,3 +245,10 @@ service-account.json deploy-*.log deployment_log.txt validation_results.json + +# Skills CLI install artifacts (regenerated by `npx skills add`) +.agents/ +skills-lock.json + +# Claude Code local state (per-user) +.claude/ diff --git a/skills/generating-custom-lightning-type/SKILL.md b/skills/generating-custom-lightning-type/SKILL.md index dadbe6e..eccaeee 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, and their editor/renderer configurations. TRIGGER when: users mention CLT, Custom Lightning Types, JSON schemas for agents, type definitions, lightning__objectType, editor/renderer configurations; users ask for a CLT with a 'widget', 'mosaic' or 'fragment' renderer (synonymous — all route to the widget rendition path)." metadata: version: "1.0" --- @@ -26,8 +26,7 @@ Custom Lightning Types (CLTs) are JSON Schema-based type definitions used by the - **Choose referenced CLT pattern for nested objects** - When you need a **reusable** or **separately deployed** nested type, create a CLT for that shape and reference it with `"lightning:type": "c__"`. That string is the referenced type’s **`lightning:type` value / FQN / registered identifier** — not the JSON Schema `title`. - **Choose standard Lightning types** when the structure is simple and can be expressed with properties and supported primitive `lightning:type` identifiers. - **Choose Apex class types** (`@apexClassType/...`) when the structure already exists server-side and you want the Apex class to define the shape. -- **Include editor/renderer config** only when you need custom UI behavior (custom LWC input/output components). Otherwise, omit. -- **Widget rendition**: After `schema.json` is drafted, you **must read references/widget-rendition.md and follow the "Widget renderer pattern"** section below for complete guidance. Do NOT attempt widget generation without reading the reference file first. +- **Include editor/renderer config** only when you need custom UI behavior (custom LWC input/output components, or widget renderer). Otherwise, omit. ## Critical Rules (Read First) - **Root object schemas MUST include**: @@ -95,6 +94,7 @@ 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 + 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. @@ -129,26 +129,17 @@ When strict validation is enabled (`unevaluatedProperties: false`), keep each pr - **Avoid known-invalid patterns**: - 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) - - **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): +4. **(Optional) Draft `renderer.json`** (only if custom UI or widget rendition is required) + - **Decide first — widget or custom LWC?** + - If the user requested `widget`, `mosaic`, or `fragment`: read `references/widget-rendition.md` and follow its workflow. The reference file owns the entire widget path — discovering blocks, fetching schemas, building the UEM tree, and writing `renderer.json`. Do not create a custom LWC for this path. + - Otherwise: use the custom LWC patterns below. + - **Supported shape (custom LWC path):** Top-level `renderer` object with `renderer.componentOverrides` and `renderer.layout`. + - **DEPRECATED**: Do NOT use `propertyRenderers` or `view`. Use `componentOverrides` and `layout`. + - **Root override pattern**: - `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. - - **Structure:** `renderer.componentOverrides["$"] = { "type": "mosaic", "definition": "tile/mosaic", "children": [ /* UEM tree of blocks and regions */ ] }` - - **REQUIRED workflow:** - - **STOP**: Do NOT attempt to create the widget renderer 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. - **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 +199,15 @@ 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 + +--- + +## Reference File Index + +| File | When to read | +|------|--------------| +| `references/widget-rendition.md` | When the user requests a CLT with a 'widget', 'mosaic', or 'fragment' renderer — read at workflow step 4 to follow the widget rendition path (block discovery, schema fetching, UEM tree, `renderer.json`). | diff --git a/skills/generating-custom-lightning-type/examples/widgets/conditional-rendering.renderer.json b/skills/generating-custom-lightning-type/examples/widgets/conditional-rendering.renderer.json new file mode 100644 index 0000000..dd17ee8 --- /dev/null +++ b/skills/generating-custom-lightning-type/examples/widgets/conditional-rendering.renderer.json @@ -0,0 +1,113 @@ +{ + "renderer": { + "componentOverrides": { + "$": { + "type": "mosaic", + "definition": "tile/mosaic", + "children": [ + { + "definition": "tile/column", + "attributes": { + "gap": "md", + "padding": "md" + }, + "children": [ + { + "definition": "tile/callout", + "meta": { + "if": "{!$attrs.showBanner}" + }, + "attributes": { + "variant": "warning", + "title": "Announcement" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "This banner is conditionally hidden.", + "variant": "body" + } + } + ] + }, + { + "definition": "tile/card", + "meta": { + "if": "{!$attrs.isVerified}" + }, + "attributes": { + "padding": "md", + "variant": "outlined" + }, + "children": [ + { + "definition": "tile/row", + "attributes": { + "gap": "sm", + "align": "center" + }, + "children": [ + { + "definition": "tile/icon", + "attributes": { + "name": "check-circle", + "color": "success" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "Welcome back, {!$attrs.name}! Your account is verified.", + "variant": "body" + } + } + ] + } + ] + }, + { + "definition": "tile/card", + "meta": { + "forEach": "{!$attrs.tasks}", + "forItem": "$task", + "if": "{!$task.isUrgent}" + }, + "attributes": { + "padding": "md", + "variant": "outlined" + }, + "children": [ + { + "definition": "tile/row", + "attributes": { + "gap": "md", + "justify": "between", + "align": "center" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "{!$task.title}", + "variant": "h4" + } + }, + { + "definition": "tile/badge", + "attributes": { + "label": "{!$task.priority}", + "variant": "secondary" + } + } + ] + } + ] + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/skills/generating-custom-lightning-type/examples/widgets/list-of-products.renderer.json b/skills/generating-custom-lightning-type/examples/widgets/list-of-products.renderer.json new file mode 100644 index 0000000..b045b0c --- /dev/null +++ b/skills/generating-custom-lightning-type/examples/widgets/list-of-products.renderer.json @@ -0,0 +1,95 @@ +{ + "renderer": { + "componentOverrides": { + "$": { + "type": "mosaic", + "definition": "tile/mosaic", + "children": [ + { + "definition": "tile/card", + "meta": { + "forEach": "{!$attrs.products}", + "forItem": "$product" + }, + "attributes": { + "padding": "md", + "variant": "outlined" + }, + "children": [ + { + "definition": "tile/row", + "attributes": { + "gap": "md", + "justify": "between", + "align": "center" + }, + "children": [ + { + "definition": "tile/column", + "attributes": { + "gap": "xs" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "{!$product.name}", + "variant": "h3" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$product.category}", + "variant": "caption", + "color": "muted" + } + } + ] + }, + { + "definition": "tile/text", + "attributes": { + "text": "${!$product.price}", + "variant": "h4", + "color": "primary" + } + } + ] + }, + { + "definition": "tile/row", + "attributes": { + "gap": "sm", + "align": "center" + }, + "meta": { + "forEach": "{!$product.features}", + "forItem": "$feature" + }, + "children": [ + { + "definition": "tile/icon", + "attributes": { + "name": "{!$feature.icon}", + "size": "sm", + "color": "success" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$feature.label}", + "variant": "caption", + "color": "muted" + } + } + ] + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/skills/generating-custom-lightning-type/examples/widgets/single-object-profile.renderer.json b/skills/generating-custom-lightning-type/examples/widgets/single-object-profile.renderer.json new file mode 100644 index 0000000..3ec5734 --- /dev/null +++ b/skills/generating-custom-lightning-type/examples/widgets/single-object-profile.renderer.json @@ -0,0 +1,131 @@ +{ + "renderer": { + "componentOverrides": { + "$": { + "type": "mosaic", + "definition": "tile/mosaic", + "children": [ + { + "definition": "tile/card", + "attributes": { + "padding": "lg", + "variant": "elevated" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "User Profile", + "variant": "h2" + } + }, + { + "definition": "tile/divider" + }, + { + "definition": "tile/column", + "attributes": { + "gap": "sm" + }, + "children": [ + { + "definition": "tile/row", + "attributes": { + "gap": "sm" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "Name:", + "variant": "body", + "weight": "semibold" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$attrs.name}", + "variant": "body" + } + } + ] + }, + { + "definition": "tile/row", + "attributes": { + "gap": "sm" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "Email:", + "variant": "body", + "weight": "semibold" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$attrs.email}", + "variant": "body" + } + } + ] + }, + { + "definition": "tile/row", + "attributes": { + "gap": "sm" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "Role:", + "variant": "body", + "weight": "semibold" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$attrs.role}", + "variant": "body" + } + } + ] + }, + { + "definition": "tile/row", + "attributes": { + "gap": "sm" + }, + "children": [ + { + "definition": "tile/text", + "attributes": { + "text": "Location:", + "variant": "body", + "weight": "semibold" + } + }, + { + "definition": "tile/text", + "attributes": { + "text": "{!$attrs.location}", + "variant": "body" + } + } + ] + } + ] + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/skills/generating-custom-lightning-type/references/widget-meta-directives.md b/skills/generating-custom-lightning-type/references/widget-meta-directives.md new file mode 100644 index 0000000..8140218 --- /dev/null +++ b/skills/generating-custom-lightning-type/references/widget-meta-directives.md @@ -0,0 +1,130 @@ +# Widget Meta Directives Reference + +The `meta` object on a UEM block carries runtime directives for iteration (`forEach` / `forItem`) and conditional rendering (`if`). Read this file when a widget needs either. For the widget workflow, layout, and metadata-action API, see `widget-rendition.md`. + +--- + +## Iteration with forEach / forItem + +`forEach` iterates over an array; the block and ALL its children repeat for each item. + +### Rules + +- Place `forEach` on the `meta` object of the REPEATING block (e.g. a row or card). +- The value is an expression referencing an array: `{!$attrs.}` (or `{!$.}` for nested loops). +- `forItem` is required alongside `forEach`. It names the variable bound to the current item and must start with `$`. +- Inside the `forEach` block, reach the **current item** through the loop variable (`{!$item.X}`) — not by traversing the array path (`{!$attrs.items.X}`, which does not unfold to the current iteration). Top-level references for values that don't change across iterations (`{!$attrs.}`) are still valid. +- `forEach` blocks can be nested — inner loops use their own `forItem` name. + +### Example — top-level list + +```json +{ + "definition": "namespace/repeatingBlock", + "meta": { "forEach": "{!$attrs.items}", "forItem": "$item" }, + "children": [ + { "definition": "namespace/childBlock1", "attributes": { "content": "{!$item.id}" } }, + { "definition": "namespace/childBlock2", "attributes": { "content": "{!$item.total}" } } + ] +} +``` + +### Example — container holds repeating child + +When a container holds repeating items, `forEach` goes on the child — not on the container. + +```json +{ + "definition": "namespace/block", + "children": [ + { + "definition": "namespace/repeatingBlock", + "meta": { "forEach": "{!$attrs.items}", "forItem": "$item" }, + "children": [ + { "definition": "namespace/childBlock", "attributes": { "content": "{!$item.name}" } } + ] + } + ] +} +``` + +### Example — nested loops + +The inner `forEach` references an array on the outer loop variable and uses a distinct `forItem` name. + +```json +{ + "definition": "namespace/repeatingBlock", + "meta": { "forEach": "{!$attrs.orders}", "forItem": "$order" }, + "children": [ + { "definition": "namespace/childBlock", "attributes": { "content": "{!$order.id}" } }, + { + "definition": "namespace/repeatingChildBlock", + "meta": { "forEach": "{!$order.lineItems}", "forItem": "$line" }, + "children": [ + { "definition": "namespace/childBlock1", "attributes": { "content": "{!$line.name}" } }, + { "definition": "namespace/childBlock2", "attributes": { "content": "{!$line.count}" } } + ] + } + ] +} +``` + +--- + +## Conditional rendering with if + +`if` conditionally renders a block. When the expression is `false`, the block and all its children are excluded from the rendered output. + +### Rules + +- Place `if` on the `meta` object of the block. +- Use `if` only when the schema has a `lightning__booleanType` property suited to the condition. Bind directly to that property (or to a loop variable holding such a value). If no suitable boolean exists in the schema, do not use `if` for that condition — render the block unconditionally instead. +- Do not lean on the truthiness of strings (`""` vs `"value"`), numbers (`0` vs `1`), or nullable fields — that may render today but is not guaranteed across surfaces. Comparisons, arithmetic, and string operations are not supported. +- `if` may coexist with `forEach` on the same `meta`. `if` is evaluated first — if `false`, the loop is skipped entirely. + +### Example — top-level boolean + +```json +{ + "definition": "namespace/block", + "meta": { "if": "{!$attrs.isVerified}" }, + "attributes": { "label": "Verified user" } +} +``` + +### Example — boolean nested inside a schema object + +```json +{ + "definition": "namespace/block", + "meta": { "if": "{!$attrs.features.showBanner}" }, + "attributes": { "text": "Promo banner" } +} +``` + +### Example — boolean inside a forEach loop + +```json +{ + "definition": "namespace/repeatingBlock", + "meta": { "forEach": "{!$attrs.tasks}", "forItem": "$task" }, + "children": [ + { "definition": "namespace/block1", "attributes": { "content": "{!$task.title}" } }, + { + "definition": "namespace/block2", // rendered only when $task.completed is true + "meta": { "if": "{!$task.completed}" }, + "attributes": { "label": "Done" } + } + ] +} +``` + +--- + +## Gotchas + +| Issue | Resolution | +|-------|-----------| +| `if` bound to a non-boolean (e.g. a string or number) does not behave as expected | Use `if` only when the schema has a `lightning__booleanType` property suited to the condition; otherwise render the block unconditionally | +| Nested loops share the same `forItem` name | Pick distinct names (e.g. `$item` outer, `$line` inner) — there is no validation error on collision | diff --git a/skills/generating-custom-lightning-type/references/widget-rendition.md b/skills/generating-custom-lightning-type/references/widget-rendition.md index 454f12f..735cc7f 100644 --- a/skills/generating-custom-lightning-type/references/widget-rendition.md +++ b/skills/generating-custom-lightning-type/references/widget-rendition.md @@ -1,124 +1,164 @@ -# Widget Generation Guide +# Widget Rendition Reference -## 📋 Overview -Widgets are reusable pieces of UI similar to templates, with placeholders for actual data values. The purpose of this file is to assist developers in creating mosaic renditions for CLTs. +How to author a UEM widget tree that renders a Custom Lightning Type's grounding schema as a `tile/mosaic` widget across surfaces. -## 🎯 Purpose -Widgets render data in a structured and unified way across various Salesforce experiences like Slack, Mobile, LEX etc. +## When to read this file -## Schema Grounding -Widget generation is **always schema-grounded** using a CLT's schema. The schema describes the data shape the widget should render. Extract property names, types, required vs optional, and nesting from the schema; then follow the full **Workflow** below, using this extracted structure to guide every step. Do not add or remove properties relative to the schema. +Read this file when the parent skill routes to widget rendition (user requested a "widget", "mosaic", or "fragment"). Do not use these patterns for custom-LWC renderers. -## ⚙️ Composition -A widget is a UEM (Unified Experience Model) tree of blocks and regions. The widget you return must follow the Typescript interfaces below: +## Scope + +- **In scope**: building the UEM tree under `renderer.componentOverrides["$"].children`, including composition, attribute binding, layout, and writing the final `renderer.json`. +- **Out of scope**: runtime meta directives (`forEach`, `forItem`, `if`) — read `references/widget-meta-directives.md`. + +--- + +## Composition + +A widget is a UEM(Unified Experience Model) tree of blocks. Every block follows this shape: ```ts -interface BlockType { - type: 'block' - definition: string // {namespace}/{blockName} - attributes?: Record - children?: (BlockType | RegionType)[] -} - -interface RegionType { - type: 'region' - name: string - children: BlockType[] +interface Block { + definition: string // {namespace}/{blockName} + attributes?: Record + meta?: { + forEach?: string // see widget-meta-directives.md + forItem?: string // see widget-meta-directives.md + if?: string // see widget-meta-directives.md + } + children?: Block[] } ``` ---- - -## 🔧 Available Metadata Actions - -### When to Use Each Action - -#### discoverUiComponents - -**Purpose:** Discover the palette of available blocks that can be used in widget composition. - -**Use for:** Finding available blocks before building your widget structure. - -**Input Parameters:** -- `actionName` (**required***): "discoverUiComponents" -- `metadataType` (**required**): "FRAGMENT" -- `parameters` (**required**): JSON object with the below fields - - `pageType` (**required**): "FRAGMENT" - - `pageContext` (optional): JSON object - not required for FRAGMENT type - - `searchQuery` (optional): String to filter components by name or description - -**Returns:** List of components with: -- `definition`: Fully qualified name (e.g., "namespace/definiton") -- `description`: Component description -- `label`: Human-readable label -- `attributes`: Optional attribute metadata - -#### getUiComponentSchemas - -**Purpose:** Get detailed JSON schemas for component configuration, including property types, required vs optional fields, and validation rules. - -**Use for:** You know which components you want but need to understand their properties before adding them to your widget. - -**Input Parameters:** -- `actionName` (**required***): "getUiComponentSchemas" -- `metadataType` (**required**): "FRAGMENT" -- `parameters` (**required**): JSON object with the below fields - - `pageType` (**required**): "FRAGMENT" - - `componentDefinitions` (**required**): List of fully qualified names (e.g., ["namespace/definition"]) - - **CRITICAL**: NEVER include "tile/mosaic" in this list. "tile/mosaic" is a container component used in renderer.json structure and **should not** be passed to getUiComponentSchemas - - `pageContext` (optional): JSON object - not required for FRAGMENT type - - `includeKnowledge` (optional): Boolean, defaults to true - includes additional component-specific guidance - -**Returns:** -- `componentSchemas`: List of results (supports partial failures) -- **Success entries**: Contains JSON schema with property definitions, types, constraints -- **Failure entries**: Contains error message explaining why schema couldn't be retrieved -- `$defs`: Schema definitions and references (if schema transformation applied) - -**Key Feature:** Supports partial failures - if some components can't be found, you still get schemas for the successful ones. --- -## Attribute binding using placeholder syntax +## Available metadata actions -- **Where to use:** When block properties must display or pass runtime data from the grounding schema, use the **Placeholder Syntax** below so that the runtime binds values into the widget. Check each block's schema (from `getUiComponentSchemas`) for the correct property name (e.g. `value`, `text`, `label`). -- **Placeholder Syntax:** Use `{!$attrs.}` as the placeholder for each block property that should receive data. - `` **must** match the property name from the grounding schema so that the runtime can resolve its value. - Example: for a schema property `title`, set the block property to `{!$attrs.title}`. -- **List / iterative data:** Only the children (list items) hold bound values; the parent list block does not. For each item inside a list (e.g. `tile/listItem`), use `{!$attrs..item}` so the runtime binds the current item. `` MUST match the schema property name of the list. Example: for `icons`, use `"{!$attrs.icons.item}"` on the list item. +### discoverUiComponents + +**Purpose**: Discover the palette of blocks available for composition. + +**Required parameters**: `actionName: "discoverUiComponents"`, `metadataType: "FRAGMENT"`, `parameters.pageType: "FRAGMENT"`. Optional: `searchQuery` to filter by name/description. + +**Returns**: list of `{ definition, description, label, attributes? }`. + +### getUiComponentSchemas + +**Purpose**: Fetch JSON schemas (property types, required vs optional, validation) for selected blocks. + +**Required parameters**: `actionName: "getUiComponentSchemas"`, `metadataType: "FRAGMENT"`, `parameters.pageType: "FRAGMENT"`, `parameters.componentDefinitions: ["namespace/definition", …]`. Optional: `includeKnowledge` (default `true`). + +**Returns**: `componentSchemas[]` (success entries carry the JSON schema, failure entries carry an error message — partial failures are supported). --- -## 💡Workflow +## Attribute binding -1. **Schema Parsing** -- Parse the schema and extract: property names, types, required vs optional, and nested structure. Use this as the **widget spec**. - -2. **Discover Available Blocks** (**REQUIRED** - do NOT skip) -- Use **discoverUiComponents metadata action** above to explore what blocks are available. -- Use property types from the **widget spec** to inform `searchQuery` (e.g. text → "text", number → "number"). - -3. **Select Components** -- Choose blocks that can represent each property in the **widget spec** from the results of step 2. - -4. **Get Component Schemas** (**REQUIRED** - do NOT skip) -- Use **getUiComponentSchemas metadata action** with the selected block definitions from step 3 and review block properties' metadata. - -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). -- Use block properties from the schemas retrieved in step 4. - -6. **Write output to CLT Bundle** -- Always write to `lightningTypes//lightningDesktopGenAi/renderer.json` (or the correct target subfolder for the product surface, e.g. `experienceBuilder/`, `lightningMobileGenAi/`, `enhancedWebChat/` when applicable). - Check **required root override pattern** below - - `renderer.componentOverrides["$"] = { "type": "mosaic", "definition": "tile/mosaic", "children": [ ... ] — array of UEM nodes - contains the widget UEM generated using the **Workflow** steps 1-5 above }` +- Bind a block property to schema data with `{!$attrs.}`. `` MUST match the property name in the grounding schema. Example: `"text": "{!$attrs.title}"`. +- When the block is inside a `forEach`, reference the loop variable instead — e.g. `"text": "{!$item.name}"`. See `references/widget-meta-directives.md`. --- -## ⚠️ Important Notes +## Layout best practices -- **widget spec** includes both required and optional attributes - review carefully to ensure valid configuration. -- 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 +These conventions cover widget *structure* — how blocks are grouped and stacked. For visual style choices, see *Styling best practices* below. + +The first child inside `tile/mosaic.children` should be a single `tile/column`. All widget content goes inside that column for predictable vertical structure across surfaces. + +| Primitive | Purpose | When to use | +|-----------|---------|-------------| +| `tile/column` | Vertical stack of children | The root wrapper, and any group of blocks that should stack | +| `tile/row` | Horizontal stack of children | Two or more blocks that belong on the same line | +| `tile/card` | Visually-boxed group | A bounded section that should read as one unit | +| `tile/divider` | Visual rule between sections | Separating major content groups (header / body / footer) | +| `tile/spacer` | Whitespace without a visible line | When extra space is needed but a divider would be too heavy; set `flex: true` to fill remaining space in a row | + +Use the five primitives above for structure; all other (content) blocks come from `discoverUiComponents`. + +- **Sectioning**: Place a `tile/divider` *or* a fresh `tile/card`-bounded section between major content groups (header → body, body → footer). Pick the divider for lightweight rules between sections of the same widget; pick a card when the section is its own logical unit. Do not use either inside a single section. +- **Nesting**: Prefer flat layouts. Only nest a `tile/column` inside a `tile/row` (or vice versa) when the visual orientation actually changes for that subgroup. + +--- + +## Styling best practices + +Widgets express *intent*, not pixels. Each surface (Slack, ChatGPT, ACC, Mobile, etc.) provides a default native look and feel, and brand/theme overrides apply automatically. Principles the widget author owns: + +- **Style semantically.** Use `variant`, `size`, and other enum-typed attributes (e.g. `primary`, `destructive`, `success`, `warning`) to express intent. Do not pin literal colors, fonts, or pixel values — that fights the surface's rendering and breaks brand/theme application. +- **One primary action per visible group.** At most one `tile/button` with `variant: primary` per visible action group. Use `secondary`, `outline`, or `ghost` for additional actions. Reserve `destructive` for genuinely destructive operations (delete, cancel a paid order, etc.). +- **One `h1` per widget.** The `h1` is the widget title. Use `h2`/`h3` for sub-section headings (skipping levels is fine if the hierarchy is shallow), `body` for prose, and `caption` for helper text. +- **Use semantic state variants on state-bearing blocks.** For `tile/alert`, `tile/badge`, `tile/callout`, and `tile/chip`, set `variant` to the semantic state (`success`, `warning`, `error`, `info`, etc.). Do not express state by overriding `text.color` on a generic block — the dedicated blocks render the correct iconography and accessibility cues for free. +- **Accept schema defaults for `gap`, `padding`, and `size`** unless there's a specific reason to override. When you do override, always pass the enum-defined token (the schema rejects pixel values and freeform strings). +- **Don't pin `width`, `height`, or `maxWidth`** unless a content constraint genuinely requires it — the surface owns layout sizing. For long text, use `truncate: true` rather than capping `maxWidth`. +- **Use the Lucide icon set.** Every `icon` attribute resolves to a name in the Lucide icon set. Pass the Lucide name (e.g. `"check"`, `"alert-circle"`); other icon libraries are not supported. + +--- + +## Workflow + +1. **Parse the schema** — extract property names, types, required vs optional, and nested structure from the CLT's grounding schema. This is the **widget spec**. +2. **Discover blocks** — call `discoverUiComponents`. Use property types from the widget spec to inform `searchQuery` (e.g. text → `"text"`, number → `"number"`). +3. **Select blocks** — choose one block per widget-spec property, plus structural primitives from *Layout best practices*. +4. **Get block schemas** — call `getUiComponentSchemas` for the selected blocks and review their property metadata. +5. **Read every matching example** — before authoring, identify which patterns the widget spec needs and read **all** matching renderer examples in `examples/widgets/`. A complex widget combines multiple patterns; read all that apply. + + | Pattern in the widget spec | Example to read | + |---|---| + | Single object (no root iteration) | `examples/widgets/single-object-profile.renderer.json` | + | Collection (root-level array iterated with `forEach`), including a nested `forEach` over an array on each item | `examples/widgets/list-of-products.renderer.json` | + | Conditional rendering (`if` bound to a boolean), including `if` + `forEach` on the same `meta` | `examples/widgets/conditional-rendering.renderer.json` | + + Use these as structural starting points, not literal templates. Compose the patterns the widget actually needs. +6. **Build the UEM tree** — + - Map each widget-spec property to a block property; preserve widget-spec order. + - **Decide root iteration**: if the schema is a single object, render its properties directly under the root `tile/column`. If the schema is a collection (top-level array), wrap the repeating block in `forEach` / `forItem`. For placement rules and example shapes, read `references/widget-meta-directives.md`. + - Bind values with placeholder syntax (see *Attribute binding* above). + - For any block that should render conditionally, add `"if"` on its `meta` — read `references/widget-meta-directives.md`. +7. **Write to the CLT bundle** — output to `lightningTypes//lightningDesktopGenAi/renderer.json` (or the surface-specific subfolder: `experienceBuilder/`, `lightningMobileGenAi/`, `enhancedWebChat/`). The renderer's root override is: + +```json +{ + "renderer": { + "componentOverrides": { + "$": { + "type": "mosaic", + "definition": "tile/mosaic", + "children": [ /* the UEM tree built in step 5 — root is tile/column */ ] + } + } + } +} +``` + +--- + +## Rules / constraints + +| Constraint | Rationale | +|-----------|-----------| +| Block definitions follow `{namespace}/{blockName}` and must match the form returned by `discoverUiComponents` | The runtime resolves blocks by exact definition string | +| Never pass `tile/mosaic` to `getUiComponentSchemas` | It is a fixed wrapper, not a queryable component | +| Always supply `parameters` (with required keys) when calling `execute_metadata_action` | Missing parameters cause a hard failure, not a partial result | +| The first child of `tile/mosaic.children` is a single `tile/column` | Predictable widget structure across surfaces | + +--- + +## Gotchas + +| Issue | Resolution | +|-------|-----------| +| `getUiComponentSchemas` returns a partial-failure entry for a block | Pick a different block from `discoverUiComponents` results; do not silently continue without a schema | +| Output written to the wrong surface subfolder | Default to `lightningDesktopGenAi/`; switch only when the user names the surface | + +--- + +## Reference File Index + +| File | When to read | +|------|--------------| +| `references/widget-meta-directives.md` | For the `forEach` / `forItem` (iteration) and `if` (conditional rendering) directives — including patterns not shown in examples (nested `forEach`, container-vs-child placement) | +| `examples/widgets/single-object-profile.renderer.json` | For the single-object pattern (root binding via `{!$attrs.X}`, no iteration) | +| `examples/widgets/list-of-products.renderer.json` | For the collection pattern (root-level `lightning__listType` iterated with `forEach`); also covers a nested `forEach` over an array per item | +| `examples/widgets/conditional-rendering.renderer.json` | For the conditional pattern (`if` on a `meta` object, including `if` + `forEach` on the same block) | + +A complex widget often combines several of these patterns (e.g. collection + nested `forEach` + conditional). Read every applicable row.