mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-07 08:04:28 +08:00
Merge f2367bb505 into 84ee08cd47
This commit is contained in:
commit
4a0041bead
7
.gitignore
vendored
7
.gitignore
vendored
@ -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/
|
||||
|
||||
@ -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__<CLTName>"`. 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/<yourRendererComponent>", "attributes": { ... } }`
|
||||
- Use `{!$attrs.<name>}` 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["<propertyName>"] = { "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/<componentName>` 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`). |
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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.<arrayAttr>}` (or `{!$<outerForItem>.<arrayField>}` 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.<unrelatedField>}`) 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 |
|
||||
@ -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<string, any>
|
||||
children?: (BlockType | RegionType)[]
|
||||
}
|
||||
|
||||
interface RegionType {
|
||||
type: 'region'
|
||||
name: string
|
||||
children: BlockType[]
|
||||
interface Block {
|
||||
definition: string // {namespace}/{blockName}
|
||||
attributes?: Record<string, any>
|
||||
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.<attrName>}` as the placeholder for each block property that should receive data.
|
||||
`<attrName>` **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.<listAttrName>.item}` so the runtime binds the current item. `<listAttrName>` 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/<TypeName>/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.<attrName>}`. `<attrName>` 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.<attrName>}` and for list properties is `{!$attrs.<listAttrName>.item}`.
|
||||
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/<TypeName>/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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user