@salesforce/afv-skills 1.66.0 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/package.json +2 -4
  2. package/skills/agentforce-offline-experimentation-run/SKILL.md +13 -28
  3. package/skills/commerce-react-storefront-create/SKILL.md +350 -0
  4. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +2 -2
  5. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +28 -16
  6. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +18 -1
  7. package/skills/education-cloud-domain-configure/SKILL.md +21 -23
  8. package/skills/education-cloud-domain-configure/references/dependent-features.md +9 -0
  9. package/skills/education-cloud-domain-configure/references/domain-features.md +15 -0
  10. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +8 -0
  11. package/skills/education-cloud-domain-configure/references/setup-widgets.md +123 -0
  12. package/skills/experience-search-coordinate/SKILL.md +2 -2
  13. package/skills/experience-search-coordinate/references/media-route.md +30 -4
  14. package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -1
  15. package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -1
  16. package/skills/platform-metadata-deploy/README.md +1 -1
  17. package/skills/platform-metadata-deploy/SKILL.md +114 -15
  18. package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +3 -4
  19. package/skills/platform-metadata-deploy/references/orchestration.md +2 -3
  20. package/skills/platform-value-set-generate/SKILL.md +19 -19
  21. package/skills/platform-widget-generate/SKILL.md +25 -51
  22. package/skills/platform-widget-generate/examples/formulas.json +19 -2
  23. package/skills/platform-widget-generate/examples/localized-labels.json +57 -0
  24. package/skills/platform-widget-generate/references/widget-actions.md +29 -0
  25. package/skills/platform-widget-generate/references/widget-bundle-validation.md +1 -0
  26. package/skills/platform-widget-generate/references/widget-formulas.md +24 -7
  27. package/skills/platform-widget-generate/references/widget-gotchas.md +10 -0
  28. package/skills/platform-widget-generate/references/widget-labels.md +78 -0
  29. package/skills/platform-widget-generate/references/widget-styling.md +12 -0
  30. package/skills/sales-call-scoring-configure/SKILL.md +17 -10
  31. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +55 -33
  32. package/skills/service-digital-engagement-channel-configure/SKILL.md +4 -2
  33. package/skills/service-digital-engagement-channel-configure/references/channel_settings.md +13 -2
  34. package/skills/service-digital-engagement-deployment-configure/SKILL.md +37 -100
  35. package/skills/service-digital-engagement-deployment-configure/assets/esd_web_update_template.xml +83 -8
  36. package/skills/service-digital-engagement-deployment-configure/examples/esd_web_prechat_fields.xml +74 -0
  37. package/skills/service-digital-engagement-deployment-configure/references/connect_api_creation.md +13 -3
  38. package/skills/service-digital-engagement-deployment-configure/references/deployment_settings.md +13 -78
  39. package/skills/service-digital-engagement-deployment-configure/references/onboarding_contract.md +5 -6
  40. package/skills/service-digital-engagement-deployment-configure/references/prechat_forms.md +245 -0
  41. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +164 -120
  42. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +61 -37
  43. package/skills/service-itsm-agentic-setup-incident-management/references/audit-tracking.md +42 -0
  44. package/skills/service-itsm-agentic-setup-incident-management/references/broadcast-communications.md +23 -0
  45. package/skills/service-itsm-agentic-setup-incident-management/references/email-to-incident.md +23 -0
  46. package/skills/service-itsm-agentic-setup-incident-management/references/incident-dashboards.md +25 -0
  47. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +30 -146
  48. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +32 -136
  49. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +32 -155
  50. package/skills/service-itsm-agentic-setup-incident-management/references/manage-incidents-with-ai.md +45 -0
  51. package/skills/service-itsm-agentic-setup-incident-management/references/priority-matrix.md +32 -0
  52. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +30 -140
  53. package/skills/service-itsm-agentic-setup-incident-management/references/sla-milestones.md +110 -0
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: platform-metadata-deploy
3
- description: "Salesforce DevOps automation using sf CLI v2. TRIGGER when: user deploys metadata, creates/manages scratch orgs or sandboxes, sets up CI/CD pipelines, or troubleshoots deployment errors with sf project deploy. DO NOT TRIGGER when: writing Apex code (use platform-apex-generate), building LWC components (use experience-lwc-generate), creating metadata definitions (use platform-custom-object-generate or platform-custom-field-generate), or querying org data (use platform-data-manage)."
3
+ description: "Salesforce DevOps automation using sf CLI v2. TRIGGER when: user deploys metadata, validates a deploy or does a dry-run, deploys to a Production org, quick-deploys a validated job ID, ships/promotes to prod, creates/manages scratch orgs or sandboxes, sets up CI/CD pipelines, or troubleshoots deployment errors with sf project deploy. DO NOT TRIGGER when: writing Apex code, building LWC components (use experience-lwc-generate), creating metadata definitions (use platform-custom-object-generate), deleting metadata from an org (use platform-destructive-deploy), or querying org data (use platform-data-manage)."
4
4
  metadata:
5
- version: "1.1"
5
+ version: "1.2"
6
6
  domains: ["Platform", "Developer Experience"]
7
7
  relatedSkills:
8
8
  - "agentforce-generate"
@@ -11,9 +11,7 @@ metadata:
11
11
  - "experience-lwc-generate"
12
12
  - "integration-connectivity-connected-app-configure"
13
13
  - "integration-connectivity-generate"
14
- - "platform-apex-generate"
15
14
  - "platform-apex-test-run"
16
- - "platform-custom-field-generate"
17
15
  - "platform-custom-object-generate"
18
16
  - "platform-data-manage"
19
17
  cliTools:
@@ -40,9 +38,9 @@ Use `platform-metadata-deploy` when the work involves:
40
38
  - troubleshooting deployment failures and dependency ordering
41
39
 
42
40
  Delegate elsewhere when the user is:
43
- - authoring Apex code → [platform-apex-generate](../platform-apex-generate/SKILL.md)
41
+ - authoring Apex code (this skill only deploys it)
44
42
  - authoring LWC components → [experience-lwc-generate](../experience-lwc-generate/SKILL.md)
45
- - creating custom objects or fields → [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md), [platform-custom-field-generate](../platform-custom-field-generate/SKILL.md)
43
+ - creating custom objects or fields → [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md)
46
44
  - building Flows → [automation-flow-generate](../automation-flow-generate/SKILL.md)
47
45
  - doing org data operations → [platform-data-manage](../platform-data-manage/SKILL.md)
48
46
  - authoring or testing Agentforce agents → [agentforce-generate](../agentforce-generate/SKILL.md)
@@ -54,6 +52,7 @@ Delegate elsewhere when the user is:
54
52
  - Use **`sf` CLI v2 only**.
55
53
  - On non-source-tracking orgs, deploy/retrieve commands require an explicit scope such as `--source-dir`, `--metadata`, or `--manifest`.
56
54
  - Prefer **`--dry-run` first** before real deploys.
55
+ - **Never run `sf project deploy start` against a production org.** Production goes through the validate → quick-deploy path below (server-side validation returns a 10-day job ID; quick-deploy promotes it without re-running tests, behind an explicit confirmation).
57
56
  - For Flows, deploy safely and activate only after validation.
58
57
  - Keep test-data creation guidance delegated to **`platform-data-manage`** after metadata is validated or deployed.
59
58
 
@@ -82,7 +81,7 @@ Ask for or infer:
82
81
  Preflight checks:
83
82
  ```bash
84
83
  sf --version
85
- sf org list
84
+ sf org list --skip-connection-status
86
85
  sf org display --target-org <alias> --json
87
86
  test -f sfdx-project.json
88
87
  ```
@@ -94,7 +93,25 @@ test -f sfdx-project.json
94
93
  ### 1. Preflight
95
94
  Confirm auth, repo shape, package directories, and target scope.
96
95
 
97
- ### 2. Validate first
96
+ ### 1a. Classify the target org (routes prod vs non-prod)
97
+
98
+ Before any deploy, classify the target from its `sf org list` record — the path forks on whether the org is **production**. Do NOT classify from `sf org display`: it reports only a plain My Domain and omits the sandbox/scratch/trial fields, so a Sandbox-or-Demo Org (SDO) is misclassified as production (forcedotcom/sf-skills#356). The `sf org list` record carries the real classification booleans. Pass `--skip-connection-status` so this stays a local read that does not refresh an OAuth token per authenticated org (forcedotcom/sf-skills#349):
99
+
100
+ ```bash
101
+ sf org list --skip-connection-status --json
102
+ ```
103
+
104
+ Find the record whose `alias`, `username`, or `orgId` matches the target, then classify from that record (first match wins):
105
+ - **Scratch** — `isScratch` is `true`.
106
+ - **Sandbox** — `isSandbox` is `true`, or the `instanceUrl` contains `--` (the My Domain sandbox marker) or `test.salesforce.com`.
107
+ - **Trial / Developer Edition** — `trailExpirationDate` (the CLI's spelling; also accept `trialExpirationDate`) is a date in the **future**, or the `instanceUrl` host contains `orgfarm-`, `.develop.my.salesforce.com`, `.pc-rnd.`, or `.scratch.my.salesforce.com`. A past date does not count — the date is cached at login and never refreshed, so a converted trial keeps a stale past date and is not a trial.
108
+
109
+ Anything else is **production** — including a **Dev Hub** (`isDevHub` is `true`) that shows none of the above signals, since Dev Hub is normally enabled in the customer's own production org. If no record matches or the record has no usable info (no `instanceUrl` and none of the flags), treat it as **unknown** and ask the user before proceeding, rather than assuming production or non-production.
110
+
111
+ - **Non-production** → the dry-run + deploy path (steps 2–6).
112
+ - **Production** → the validate → quick-deploy path ([Production deploys](#production-deploys-validate--quick-deploy)). Never run `sf project deploy start` against production.
113
+
114
+ ### 2. Validate first (non-production)
98
115
  ```bash
99
116
  sf project deploy start --dry-run --source-dir force-app --target-org <alias> --wait 30 --json
100
117
  ```
@@ -118,9 +135,6 @@ sf project deploy start --manifest manifest/package.xml --target-org <alias> --t
118
135
 
119
136
  # manifest deploy with Spring '26 relevant-test selection
120
137
  sf project deploy start --manifest manifest/package.xml --target-org <alias> --test-level RunRelevantTests --wait 30 --json
121
-
122
- # quick deploy after successful validation
123
- sf project deploy quick --job-id <validation-job-id> --target-org <alias> --json
124
138
  ```
125
139
 
126
140
  ### 5. Verify
@@ -136,6 +150,93 @@ Output template: [references/deployment-report-template.md](references/deploymen
136
150
 
137
151
  ---
138
152
 
153
+ ## Production deploys (validate → quick-deploy)
154
+
155
+ Production is never deployed with `sf project deploy start`. It is a two-step promotion: a **server-side validation** produces a job ID, then a **quick deploy** promotes that validated job without re-running tests, behind an explicit confirmation.
156
+
157
+ ### P1. Validate against production
158
+
159
+ Production validations REQUIRE a test level. Use `RunLocalTests` by default; switch to `RunSpecifiedTests --tests <ClassName>...` only if the user has explicitly listed tests.
160
+
161
+ ```bash
162
+ sf project deploy validate --target-org <alias> --json [scope flags] --test-level RunLocalTests
163
+ ```
164
+
165
+ Scope flags — use exactly one: `--source-dir <path>`, `--metadata <Type:Name>`, or `--manifest manifest/package.xml`.
166
+
167
+ The response returns a **job ID** (`result.id`) valid for **10 days**. Persist it **with the target's immutable identity** — the org ID and instance URL from `sf org display`, not just the alias — so the quick deploy can prove the validation belongs to the org it is about to promote to:
168
+
169
+ ```bash
170
+ mkdir -p .sfdx
171
+ ORG_JSON=$(sf org display --target-org <alias> --json)
172
+ ORG_ID=$(printf '%s' "$ORG_JSON" | jq -r '.result.id')
173
+ INSTANCE_URL=$(printf '%s' "$ORG_JSON" | jq -r '.result.instanceUrl')
174
+ printf '{"jobId":"%s","createdAt":"%s","targetOrg":"%s","orgId":"%s","instanceUrl":"%s","testLevel":"RunLocalTests"}\n' \
175
+ "<id>" "<iso8601>" "<alias>" "$ORG_ID" "$INSTANCE_URL" > .sfdx/last-validation.json
176
+ ```
177
+
178
+ On validation failure, triage from `result.details.componentFailures` and `result.details.runTestResult.failures` — surface the top component errors and test failures with a scoped fix direction, then STOP. Never use `--ignore-errors`/`--ignore-warnings` during validation.
179
+
180
+ ### P2. Gate the quick deploy strictly
181
+
182
+ Before promoting, verify all five:
183
+ 1. **Target is production** — re-confirm via step 1a's classification.
184
+ 2. **A validation exists** — read `.sfdx/last-validation.json`, or ask for the job ID, or fall back to `--use-most-recent`. Resolve `--use-most-recent` to a concrete job ID **now** (do not carry it forward as an unresolved flag), so the checks below run against a known job.
185
+ 3. **Validation belongs to THIS target** — re-run `sf org display --target-org <alias> --json` and require the current `result.id` (org ID) to equal the `orgId` persisted in `.sfdx/last-validation.json`. This closes the stale-cache hazard: a cached job ID resolves its org from the deploy cache, so without this check the banner can show org B while the job promotes to org A. On any mismatch — or if the persisted record lacks an `orgId`/`instanceUrl` to compare — treat it as a **hard stop** and re-validate (P1) against the intended target. Never promote a validation whose target you cannot prove.
186
+ 4. **Validation is fresh** — explicit `--job-id` ≤ 10 days; a `--use-most-recent` resolution ≤ 3 days. If stale, re-validate (P1) first.
187
+ 5. **Explicit user confirmation** — print the banner below and require a literal "yes". Populate every banner field from the verified validation record and the just-fetched org data — never from unverified input. Never auto-confirm.
188
+
189
+ ```text
190
+ ┌─ PRODUCTION DEPLOY ─────────────────────────────┐
191
+ │ Org alias: <alias> │
192
+ │ Instance: <instanceUrl> │
193
+ │ Edition: <edition> │
194
+ │ Validation ID: <jobId> │
195
+ │ Validated: <createdAt> (X days ago) │
196
+ │ Components: <componentCount> queued │
197
+ │ Tests: <run>/<passed>/<failed> │
198
+ └─────────────────────────────────────────────────┘
199
+ Confirm deploy to PRODUCTION? (yes/no)
200
+ ```
201
+
202
+ `sf org display --json` does not return the edition. Read `<edition>` from the Organization object's `OrganizationType` (e.g. `Enterprise Edition`):
203
+
204
+ ```bash
205
+ sf data query --query "SELECT OrganizationType FROM Organization" --target-org <alias> --json | jq -r '.result.records[0].OrganizationType'
206
+ ```
207
+
208
+ ### P3. Promote and audit
209
+
210
+ The literal "yes" from P2's banner is the authorization. In the **Salesforce development plugin** context, a PreToolUse hook (`sf-deploy-gate`) blocks a bare production `deploy quick` and only permits it when the command carries the machine-readable confirmation marker `CONFIRM_PROD=1`. Emit that marker to transmit the confirmation the user just gave — it is **not** an independent bypass, so add it **only after** the explicit "yes", never before:
211
+
212
+ ```bash
213
+ # plugin context: CONFIRM_PROD=1 relays P2's confirmation to the safety hook
214
+ CONFIRM_PROD=1 sf project deploy quick --job-id <validation-id> --target-org <alias> --wait 30 --json
215
+ ```
216
+
217
+ Outside the plugin (bare CLI, headless/MCP) there is no such hook — run the same command without the marker.
218
+
219
+ **Quick deploy starts a new deployment with its own job ID** — it does not reuse the validation ID. Capture the response, extract the new ID, and use *that* for polling, reporting, and the audit filename; keep the validation ID separately for traceability. Stop if the response carries no deployment ID:
220
+
221
+ ```bash
222
+ QUICK_RESULT=$(CONFIRM_PROD=1 sf project deploy quick --job-id <validation-id> --target-org <alias> --wait 30 --json)
223
+ QUICK_DEPLOY_ID=$(printf '%s' "$QUICK_RESULT" | jq -r '.result.id')
224
+ [ -n "$QUICK_DEPLOY_ID" ] && [ "$QUICK_DEPLOY_ID" != "null" ] || { echo "No quick-deploy ID returned — do not report the validation ID as the deploy; investigate."; exit 1; }
225
+ ```
226
+
227
+ Quick deploy promotes the validated components and does NOT re-run tests. Persist the report **for the quick-deploy operation** (not the validation) for the audit trail, then guide post-deploy verification (smoke-test critical paths, watch Setup → Deployment Status, prepare a rollback plan if anything regressed):
228
+
229
+ ```bash
230
+ mkdir -p .sfdx/deploy-history
231
+ sf project deploy report --job-id "$QUICK_DEPLOY_ID" --target-org <alias> --json > ".sfdx/deploy-history/${QUICK_DEPLOY_ID}.json"
232
+ ```
233
+
234
+ If quick deploy times out or stays in progress, poll with the **quick-deploy ID**, not the validation ID.
235
+
236
+ Adding `CONFIRM_PROD=1` after an explicit confirmation is the sanctioned path — it is not "working around" the gate. But never override a denial the user has **not** confirmed, and never override one raised for a different reason — a target mismatch (P2 check 3), a destructive deploy, or an unknown/unresolved org. Surface those denials and stop.
237
+
238
+ ---
239
+
139
240
  ## High-Signal Failure Patterns
140
241
 
141
242
  | Error / symptom | Likely cause | Default fix direction |
@@ -163,7 +264,7 @@ Default pipeline shape:
163
264
  7. verify + notify
164
265
 
165
266
  - When org policy and release risk allow it, consider `--test-level RunRelevantTests` for Apex-heavy deployments.
166
- - Pair this with modern Apex test annotations such as `@IsTest(testFor=...)` and `@IsTest(isCritical=true)` — see [platform-apex-generate](../platform-apex-generate/SKILL.md) for authoring guidance.
267
+ - Pair this with modern Apex test annotations such as `@IsTest(testFor=...)` and `@IsTest(isCritical=true)`.
167
268
 
168
269
  Static analysis now uses **Code Analyzer v5** (`sf code-analyzer`), not retired `sf scanner`.
169
270
 
@@ -185,9 +286,7 @@ For full agent DevOps details, including `Agent:` pseudo metadata, publish/activ
185
286
 
186
287
  | Need | Delegate to | Reason |
187
288
  |---|---|---|
188
- | custom object creation | [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md) | define objects before deploy |
189
- | custom field creation | [platform-custom-field-generate](../platform-custom-field-generate/SKILL.md) | define fields before deploy |
190
- | Apex authoring / fixes | [platform-apex-generate](../platform-apex-generate/SKILL.md) | code authoring and repair |
289
+ | custom object / field creation | [platform-custom-object-generate](../platform-custom-object-generate/SKILL.md) | define objects and fields before deploy |
191
290
  | run tests after deployment | [platform-apex-test-run](../platform-apex-test-run/SKILL.md) | post-deploy test execution and coverage |
192
291
  | Flow creation / repair | [automation-flow-generate](../automation-flow-generate/SKILL.md) | Flow authoring and activation guidance |
193
292
  | test data or seed records | [platform-data-manage](../platform-data-manage/SKILL.md) | describe-first data setup and cleanup |
@@ -366,9 +366,9 @@ Pass the BotVersion number you intend to activate as the third argument.
366
366
  **Critical:** Dependencies must be deployed BEFORE the agent.
367
367
 
368
368
  ```text
369
- 1. Custom Objects/Fields (platform-custom-object-generate, platform-custom-field-generate)
369
+ 1. Custom Objects/Fields (platform-custom-object-generate)
370
370
  ↓
371
- 2. Apex Classes (platform-apex-generate)
371
+ 2. Apex Classes
372
372
  ↓
373
373
  3. Flows (automation-flow-generate)
374
374
  ↓
@@ -571,14 +571,13 @@ sf agent activate --api-name My_Agent --target-org myorg
571
571
  | From Skill | To Skill | Purpose |
572
572
  |------------|----------|---------|
573
573
  | agentforce-generate | platform-metadata-deploy | Publish and activate agents |
574
- | platform-apex-generate | platform-metadata-deploy | Deploy Apex before agent |
575
574
  | automation-flow-generate | platform-metadata-deploy | Deploy Flows before agent |
576
575
  | integration-connectivity-generate | platform-metadata-deploy | Deploy Named Credentials for external APIs |
577
576
 
578
577
  ### Integration Pattern
579
578
 
580
579
  ```bash
581
- # 1. platform-apex-generate creates InvocableMethod class
580
+ # 1. An InvocableMethod class is authored
582
581
  # 2. automation-flow-generate creates wrapper Flow
583
582
  # 3. agentforce-generate creates agent with flow:// action
584
583
  # 4. platform-metadata-deploy orchestrates deployment in correct order
@@ -11,7 +11,7 @@ This document details how platform-metadata-deploy fits into the multi-skill wor
11
11
  ┌─────────────────────────────────────────────────────────────────────────────┐
12
12
  │ STANDARD MULTI-SKILL ORCHESTRATION ORDER │
13
13
  ├─────────────────────────────────────────────────────────────────────────────┤
14
- │ 1. platform-custom-object-generate / platform-custom-field-generate │
14
+ │ 1. platform-custom-object-generate │
15
15
  │ └── Create object/field definitions (LOCAL files) │
16
16
  │ │
17
17
  │ 2. automation-flow-generate │
@@ -84,7 +84,7 @@ When deploying agents with external API integrations:
84
84
  ├─────────────────────────────────────────────────────────────────────────────┤
85
85
  │ 1. integration-connectivity-connected-app-configure → Create OAuth Connected App │
86
86
  │ 2. integration-connectivity-generate → Create Named Credential + External Service │
87
- │ 3. platform-apex-generate → Create @InvocableMethod (if needed) │
87
+ │ 3. Apex authoring → Create @InvocableMethod (if needed) │
88
88
  │ 4. automation-flow-generate → Create Flow wrapper │
89
89
  │ │
90
90
  │ 5. platform-metadata-deploy ◀── FIRST DEPLOYMENT │
@@ -169,7 +169,6 @@ sf agent activate --api-name AgentName --version N --target-org alias --json
169
169
  |------------|--------------|------|
170
170
  | platform-custom-object-generate | → platform-metadata-deploy | "Deploy objects to [org]" |
171
171
  | automation-flow-generate | → platform-metadata-deploy | "Deploy flow with --dry-run" |
172
- | platform-apex-generate | → platform-metadata-deploy | "Deploy classes with RunLocalTests" |
173
172
  | agentforce-generate | → platform-metadata-deploy | "Deploy and publish agent" |
174
173
 
175
174
  ---
@@ -37,7 +37,7 @@ For authoritative Metadata API type, field, and enum details beyond this skill,
37
37
  | Root element | `<GlobalValueSet>` | `<StandardValueSet>` |
38
38
  | Name source | filename (dev name) | `<fullName>` = fixed catalog name |
39
39
  | Value element | `<customValue>` | `<standardValue>` |
40
- | Can add NEW values? | Yes | No — modify existing only |
40
+ | Can add NEW values? | Yes | Yes — copy the attribute pattern of an existing value |
41
41
  | Can create a new NAME? | Yes | No — only the fixed catalog |
42
42
  | `*` wildcard in package.xml | Supported | Not supported |
43
43
 
@@ -50,7 +50,7 @@ For authoritative Metadata API type, field, and enum details beyond this skill,
50
50
  This document defines the mandatory constraints for generating value-set metadata XML. The agent must verify these constraints before outputting XML to prevent Metadata API deployment errors.
51
51
 
52
52
  - **GlobalValueSet** — a reusable, named set of picklist values defined once and referenced by any number of picklist/multi-select fields. Use when the same value list is shared across multiple fields.
53
- - **StandardValueSet** — the value list behind a Salesforce-defined standard picklist (Industry, Lead Source, etc.). You can only **modify** the values in a fixed catalog of named sets; you cannot invent a new set or add brand-new values.
53
+ - **StandardValueSet** — the value list behind a Salesforce-defined standard picklist (Industry, Lead Source, etc.). You can only customize the fixed catalog of named sets, so you cannot invent a new set name, but you can modify existing values and add new ones.
54
54
 
55
55
  ---
56
56
 
@@ -142,16 +142,16 @@ In API 57.0+ orgs the platform *stores/displays* a GlobalValueSet's developer na
142
142
 
143
143
  #### HARD CONSTRAINTS — read before generating
144
144
 
145
- 1. **You can ONLY modify values inside the fixed catalog of named standard value sets.** You **cannot** add a brand-new value, and you **cannot** create a new StandardValueSet name. The Metadata API will reject both.
145
+ 1. **A StandardValueSet name must come from the fixed catalog of named standard value sets.** You **cannot** create a new StandardValueSet name. You **can** add a new value to an existing standard set, but copy the full attribute pattern of an existing value in that set (for example `closed`, `forecastCategory`, `probability` and `won` on `OpportunityStage`), because omitted attributes are filled with platform defaults that can be wrong.
146
146
  2. The root carries a `<fullName>` whose value is the **fixed enum name** (e.g. `Industry`), NOT a `masterLabel`. The filename must match this name.
147
147
  3. Values are `<standardValue>` entries — **not** `<customValue>`.
148
- 4. **Emit ONLY the values the request explicitly names — a surgical, minimal change.** Include a `<standardValue>` block for each value the user asks you to activate, deactivate, relabel, or reorder, and **nothing else**. Do **not** enumerate the full picklist or emit `<standardValue>` entries for values the request did not mention. A StandardValueSet deployment is a partial update: unlisted values keep their current org state untouched. Reproducing every value (e.g. all 30+ Industry entries) is noise and risks clobbering org state — it is wrong even when the request says "keep *only* X active," which means "set the named ones; leave the rest as-is," not "enumerate and deactivate everything else." If you use the grounding MCP to discover existing values, use it only to confirm the named values exist and to get their exact `<fullName>`/`<label>` — not as a list to reproduce in full.
148
+ 4. **A StandardValueSet write REPLACES the whole set. Send every existing value and change only what the request names.** Read the current set first (grounding MCP or a retrieve), then include a `<standardValue>` block for every existing value, with the requested activations, deactivations, relabels or additions applied. A value you leave out is deactivated on the field: a deploy that named only `Web` in `CaseOrigin` left `Email`, `Phone` and `Agentforce` inactive. Never send only the named values, even when the set is large (for example all 30+ `Industry` entries). Request wording such as "keep *only* X active" means set X active and every other value inactive, so state that intent in the result. Verify by reading the set back and diffing it against the read taken before the write.
149
149
 
150
150
  #### `<standardValue>` — Modifiable Sub-Elements
151
151
 
152
152
  | Sub-element | Modifiable? | Notes |
153
153
  |-------------|-------------|-------|
154
- | `<fullName>` | Identifies the value (must already exist) | Cannot introduce a new one |
154
+ | `<fullName>` | Identifies the value | An existing value, or a new one you are adding |
155
155
  | `<label>` | Yes | Relabel the value's UI text |
156
156
  | `<isActive>` | Yes | `false` deactivates; omit or `true` keeps active |
157
157
  | `<default>` | Yes | At most one value `true` |
@@ -165,7 +165,7 @@ In API 57.0+ orgs the platform *stores/displays* a GlobalValueSet's developer na
165
165
  > `https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/standardvalueset_names.htm`.
166
166
  > If the name is not in that appendix, it is **not** a StandardValueSet — it is either a GlobalValueSet or an inline CustomField picklist.
167
167
 
168
- #### CORRECT — StandardValueSet (modify existing values only)
168
+ #### CORRECT — StandardValueSet (the complete set, with the requested change applied)
169
169
 
170
170
  ```xml
171
171
  <?xml version="1.0" encoding="UTF-8"?>
@@ -183,6 +183,7 @@ In API 57.0+ orgs the platform *stores/displays* a GlobalValueSet's developer na
183
183
  <label>Agriculture</label>
184
184
  <isActive>false</isActive> <!-- deactivated, but kept in the set -->
185
185
  </standardValue>
186
+ <!-- ... every other existing standardValue entry repeated unchanged ... -->
186
187
  </StandardValueSet>
187
188
  ```
188
189
 
@@ -192,23 +193,23 @@ In API 57.0+ orgs the platform *stores/displays* a GlobalValueSet's developer na
192
193
  <StandardValueSet xmlns="http://soap.sforce.com/2006/04/metadata">
193
194
  <masterLabel>Industry</masterLabel> <!-- WRONG: StandardValueSet uses <fullName>, not masterLabel -->
194
195
  <customValue> <!-- WRONG: uses <standardValue>, not customValue -->
195
- <fullName>Renewable Energy</fullName> <!-- WRONG: cannot ADD a new value to a standard set -->
196
+ <fullName>Renewable Energy</fullName>
196
197
  <label>Renewable Energy</label>
197
198
  </customValue>
198
199
  </StandardValueSet>
199
200
  ```
200
201
 
201
- **Errors:** unknown element `masterLabel`/`customValue`; adding a value not already in the standard catalog fails deployment.
202
+ **Errors:** unknown element `masterLabel`/`customValue` for a StandardValueSet.
202
203
 
203
204
  ---
204
205
 
205
206
  ### 4. Never Invent Values — Verify, Don't Hallucinate CRITICAL
206
207
 
207
- When customizing a **StandardValueSet** (or extending a shared GlobalValueSet), **only modify values that already exist** — never invent the value list of a standard picklist. The hard rule is about what you EMIT: a `<standardValue>` whose `<fullName>` is not a real catalog value will fail deployment.
208
+ When customizing a **StandardValueSet** (or extending a shared GlobalValueSet), **never invent the existing value list of a standard picklist**. Existing values must be repeated exactly as the org has them, and a new value is added only when the request names it.
208
209
 
209
- For well-known standard picklists you already know the canonical values (e.g. `Industry`, `LeadSource`, `OpportunityStage`). When you are unsure a named value exists, you can confirm it against the live org — but treat lookup as a *confirmation* step, not a required first call:
210
+ For well-known standard picklists you already know the canonical values (e.g. `Industry`, `LeadSource`, `OpportunityStage`). When you are unsure which values exist, you can confirm them against the live org — but treat lookup as a *confirmation* step, not a required first call:
210
211
 
211
- - **Grounding MCP** (if available) exposes `search_metadata` and `query_metadata` to look up live metadata. Use them only to confirm a named value's exact `<fullName>`/`<label>` — not to pull the full list to reproduce.
212
+ - **Grounding MCP** (if available) exposes `search_metadata` and `query_metadata` to look up live metadata. Use them to read the complete current set, because a write must repeat every existing value with its exact `<fullName>`/`<label>`.
212
213
  - **CLI fallback** — query the Tooling API directly:
213
214
 
214
215
  ```bash
@@ -216,7 +217,7 @@ sf data query --use-tooling-api \
216
217
  --query "SELECT MasterLabel, Metadata FROM StandardValueSet WHERE MasterLabel = '<name>'"
217
218
  ```
218
219
 
219
- > **The point is the output, not the lookup:** emit modifications ONLY to values you know exist. A generated StandardValueSet that introduces unseen values is a hallucination and will fail deployment. (This pairs with the minimal-scope rule in §3: confirm the *named* values; don't enumerate the whole set.)
220
+ > **The point is the output, not the lookup:** the written set must contain every existing value unchanged, plus only the changes the request names. Do not invent values the request did not ask for. (This pairs with rule 4 in §3: a write replaces the whole set.)
220
221
 
221
222
  ---
222
223
 
@@ -247,17 +248,16 @@ The agent must **reject** and explain — not silently "fix" by inventing metada
247
248
 
248
249
  | Violation | Action / Message |
249
250
  |-----------|------------------|
250
- | Add a NEW value to a **StandardValueSet** | Reject. "Standard value sets cannot accept new values. Create a **GlobalValueSet** (reusable) or an inline picklist on a **CustomField** instead." |
251
251
  | Create a NEW StandardValueSet name | Reject. The name must be in the standard catalog appendix. Otherwise it is a GlobalValueSet. |
252
252
  | **Value set developer name** with spaces / invalid chars | Convert spaces to underscores; must start with a letter; alphanumeric + underscore only. `Priority Levels` → `Priority_Levels`. (This applies to the value SET name and the field API name — NOT to individual `<customValue><fullName>` values, which keep spaces as written.) |
253
253
  | Duplicate value `fullName` within one set | Reject. Each `fullName` must be unique within the value set. |
254
254
  | More than one `<default>true</default>` | Reject. At most one default value per set. |
255
255
 
256
- #### INCORRECT — adding a value to a standard set
256
+ #### Adding a value to a standard set
257
257
 
258
258
  > "Add a `Cryptocurrency` value to the Industry picklist."
259
259
 
260
- Do not emit a `<standardValue>` with `fullName` `Cryptocurrency`. Respond that standard value sets are a fixed catalog and propose a GlobalValueSet (if reused across fields) or an inline restricted picklist on a single CustomField.
260
+ This is allowed. Read the complete current `Industry` set, then write it back with every existing value unchanged plus one new `<standardValue>` for `Cryptocurrency`. Copy the attribute pattern of an existing value in the set, because omitted attributes get platform defaults that can be wrong.
261
261
 
262
262
  ---
263
263
 
@@ -275,7 +275,7 @@ A value set must deploy **before** any CustomField that references it.
275
275
 
276
276
  | Error / Symptom | Cause | Fix |
277
277
  |-----------------|-------|-----|
278
- | Value not added to standard picklist | Tried to ADD a value to a `StandardValueSet` | Standard sets are fixed; use GlobalValueSet or inline CustomField picklist |
278
+ | Other values of a standard picklist became inactive after a deploy | The deploy listed only some values, and a write replaces the whole set | Resend the complete set with every existing value (§3 rule 4) |
279
279
  | `Required field missing: sorted` | GlobalValueSet missing `<sorted>` | Add `<sorted>true</sorted>` or `<sorted>false</sorted>` |
280
280
  | Unknown element `masterLabel` (StandardValueSet) | Used `masterLabel` instead of `fullName` | StandardValueSet root uses `<fullName>` = catalog name |
281
281
  | Unknown element `customValue` (StandardValueSet) | Used `customValue` instead of `standardValue` | Use `<standardValue>` in standard sets |
@@ -301,12 +301,12 @@ Before generating value-set XML, verify:
301
301
  - [ ] When referencing from a field, is `<valueSetName>` the **bare** developer name with NO `__gvs` suffix?
302
302
 
303
303
  ### StandardValueSet Checks CRITICAL
304
- - [ ] Are you emitting modifications ONLY to values you know exist (confirmed from known standard catalogs, or via grounding `search_metadata`/`query_metadata` / Tooling API if unsure) — never invented values?
304
+ - [ ] Did you read the complete current set (grounding `search_metadata`/`query_metadata` / Tooling API / retrieve) so every existing value is repeated exactly?
305
305
  - [ ] Is the root `<StandardValueSet>` with the correct namespace?
306
306
  - [ ] Does the root use `<fullName>` set to the fixed catalog name (NOT `masterLabel`)?
307
307
  - [ ] Are values `<standardValue>` entries (NOT `customValue`)?
308
- - [ ] Are you ONLY modifying values that already exist (no new `fullName`)?
309
- - [ ] Did you avoid adding a brand-new value or a new set name?
308
+ - [ ] Does the written set contain every existing value, with only the changes the request names applied?
309
+ - [ ] Did you avoid a new set name, and for any new value did you copy the attribute pattern of an existing value?
310
310
 
311
311
  ### Shared Checks
312
312
  - [ ] At most one value has `<default>true</default>`?
@@ -119,6 +119,7 @@ On success, read `[0].outputValues`.
119
119
  - Bind a block property to runtime data with `{!$attrs.<attrName>}`. `<attrName>` MUST match a property name in `schema.json`.
120
120
  - Inside a `forEach`, reference the loop variable instead — e.g. `"text": "{!$item.name}"`. See `references/widget-meta-directives.md`.
121
121
  - A binding may be a **formula expression** instead of a bare field reference. See `references/widget-formulas.md` for the full supported-function list, syntax, and gotchas.
122
+ - **Localization:** only when the user asks for localized or translated text (or names Custom Labels / `$Label`), write static, user-visible strings as Custom Label references — `{!$Label.c.<LabelName>}`. See `references/widget-labels.md`.
122
123
 
123
124
  ---
124
125
 
@@ -131,28 +132,7 @@ On success, read `[0].outputValues`.
131
132
  | `action/openLink` | synchronous | `url` (string). Optional `target` (`_blank`\|`_self`, default `_blank`) | Opens a URL |
132
133
  | `action/sendMessage` | asynchronous | `content` (string) | Posts a new user message back to the agent and earns a fresh turn |
133
134
 
134
- ```json
135
- {
136
- "definition": "tile/button",
137
- "attributes": {
138
- "label": "Button Label",
139
- "variant": "primary",
140
- "actions": { "click": [ { "definition": "action/sendMessage", "attributes": { "content": "content" } } ] }
141
- }
142
- }
143
- ```
144
-
145
- ```json
146
- {
147
- "definition": "tile/button",
148
- "attributes": {
149
- "label": "Button Label",
150
- "variant": "primary",
151
- "actions": { "click": [ { "definition": "action/openLink", "attributes": { "url": "https://www.example.com", "target": "_blank" } } ] }
152
- }
153
- }
154
- ```
155
-
135
+ See `references/widget-actions.md` for complete `sendMessage` and `openLink` button JSON.
156
136
 
157
137
  **A `tile/button` with no `actions` attribute renders disabled** — a button exists to trigger an action, so always attach a `click` action to a button meant to be interactive.
158
138
 
@@ -175,16 +155,7 @@ These conventions cover widget *structure* — how blocks are grouped and stacke
175
155
 
176
156
  ## Styling Best Practices
177
157
 
178
- Widgets express *intent*, not pixels. Each surface provides a default look and feel; brand/theme overrides apply automatically.
179
-
180
- - **Style semantically.** Use `variant`, `size`, and other enum-typed attributes (`primary`, `destructive`, `success`, `warning`). Do not pin literal colors or pixel values.
181
- - **One primary action per visible group.** At most one `tile/button` with `variant: primary`. Use `secondary` or `destructive` for additional actions (see the `tile/button` schema for the full variant enum).
182
- - **Every `tile/button` needs a `click` action.** See *Actions* above — an action-less button renders disabled.
183
- - **One `h1` per widget.** Use `h2`/`h3` for sub-section headings, `body` for prose, `caption` for helper text.
184
- - **Use semantic state variants on state-bearing blocks** (`tile/badge`, `tile/callout`).
185
- - **Accept schema defaults for `gap`, `size`** unless there is a specific reason to override.
186
- - **Don't pin `width`** unless a content constraint requires it.
187
- - **Use the Lucide icon set.** Pass the Lucide name (`"check"`, `"alert-circle"`); other icon libraries are not supported.
158
+ Style semantically (variants, not literal colors or pixels), one primary button per group, one `h1` per widget, Lucide icons only. Read `references/widget-styling.md` before assigning variants, sizes, or icons.
188
159
 
189
160
  ---
190
161
 
@@ -198,7 +169,7 @@ Widgets express *intent*, not pixels. Each surface provides a default look and f
198
169
 
199
170
  3. **Select blocks.** Choose one block per widget-spec property, plus structural primitives from *Layout Best Practices*.
200
171
 
201
- 4. **Get block schemas (REQUIRED — do NOT skip).** Call the `getUiComponentSchemas` invocable action for the selected blocks. Review property metadata. **If `isSuccess` is `false`, `componentSchemas` is empty, or any entry has `success: false`, do not use that block — re-select a different one from step 2's results; if none works, STOP and surface the error. Never improvise from existing widgets in the project.**
172
+ 4. **Get block schemas (REQUIRED — do NOT skip).** Call the `getUiComponentSchemas` invocable action for the selected blocks. Review property metadata, including each property's `default` — omit any attribute whose value would equal it (see `references/widget-styling.md`). **If `isSuccess` is `false`, `componentSchemas` is empty, or any entry has `success: false`, do not use that block — re-select a different one from step 2's results; if none works, STOP and surface the error. Never improvise from existing widgets in the project.**
202
173
 
203
174
  5. **Build the UEM tree (example reads REQUIRED — do NOT skip).** First, identify which patterns match the widget spec and read each matching example file from this skill's own `examples/` directory (`<skill-root>/examples/`):
204
175
 
@@ -207,12 +178,15 @@ Widgets express *intent*, not pixels. Each surface provides a default look and f
207
178
  | Single object (no iteration) | `<skill-root>/examples/single-object.json` |
208
179
  | Any list iteration (root-level array, nested list, or list embedded in a single-object widget) | `<skill-root>/examples/list-with-foreach.json` |
209
180
  | Conditional rendering (`if` bound to a boolean or formula) | `<skill-root>/examples/conditional.json` |
210
- | Computed values, text/number transforms, or `if` driven by a formula (not a bare boolean field) | `<skill-root>/examples/formulas.json` |
181
+ | Computed values, text/number transforms, locale-formatted numbers/currency/dates, or `if` driven by a formula (not a bare boolean field) | `<skill-root>/examples/formulas.json` |
182
+ | The user asked for localized / translated text (Custom Labels) | `<skill-root>/examples/localized-labels.json` |
211
183
 
212
184
  A spec may match multiple patterns (e.g. a list of items where some items render conditionally reads both `list-with-foreach.json` and `conditional.json`). **Read every matching example, and only those — do not skip the read because the pattern feels familiar.**
213
185
 
214
186
  **Before writing any formula**, read `references/widget-formulas.md` in full — it is the authoritative list of supported operators and functions; do not improvise a function name or operator that isn't documented there.
215
187
 
188
+ **Before writing any `$Label` reference**, read `references/widget-labels.md` in full.
189
+
216
190
  Then:
217
191
  - Map each widget-spec property to a block property; preserve spec order.
218
192
  - **Decide root iteration:** single object → properties directly under root `tile/column`. Collection → wrap repeating block in `forEach`/`forItem`. See `references/widget-meta-directives.md`.
@@ -264,6 +238,8 @@ Widgets express *intent*, not pixels. Each surface provides a default look and f
264
238
  9. **Self-validate.** Before reporting, run the checks and report each result individually (`pass` or `fail (<reason>)`). Do **not** summarize as a single "all passed" line — list every check so a reviewer can spot a silent skip.
265
239
  Run every check in `references/widget-bundle-validation.md`.
266
240
 
241
+ 10. **Report labels.** If the widget uses `$Label`, end the response with the label report described in `references/widget-labels.md`.
242
+
267
243
  ---
268
244
 
269
245
  ## Rules / Constraints
@@ -281,27 +257,25 @@ Run every check in `references/widget-bundle-validation.md`.
281
257
 
282
258
  ## Gotchas
283
259
 
284
- | Issue | Resolution|
285
- |---|---|
286
- | `getUiComponentSchemas` returns a partial-failure entry | Pick a different block from `discoverUiComponents`; do not silently continue without a schema|
287
- | Body references `{!$attrs.foo}` but `foo` is not under `schema.json` `properties.attributes.properties` | Add `foo` to `schema.json` `properties.attributes.properties` OR remove the body reference|
288
- | Output written outside `<pkgDir>/uiWidgets/<widgetName>/` | `<pkgDir>` = `<packageDirectories[].path>/main/default` (see `references/widget-bundle-layout.md`). Dropping the `main/default/` segment is the common cause of widgets landing at `force-app/uiWidgets/...` instead of `force-app/main/default/uiWidgets/...` |
289
- | `if` bound directly to a raw string or number relies on truthiness and is unreliable | Bind to a `lightning__booleanType` property, or use a formula comparison that evaluates to boolean|
290
- | `tile/button` renders but does nothing when clicked | No `actions.click` entry was set — an action-less button renders disabled by design. Add one (see *Actions* above)|
291
- | Using `action/sendMessage` for pure navigation, or `action/openLink` when the agent should respond | `action/openLink` is synchronous and does not consume a turn; `action/sendMessage` is asynchronous and earns a fresh turn. Pick the one matching the intended UX|
260
+ Known failure modes and fixes (partial schemas, unresolved bindings, wrong output path, `if` truthiness, dead buttons, action choice): see `references/widget-gotchas.md`.
292
261
 
293
262
  ---
294
263
 
295
264
  ## Reference File Index
296
265
 
297
- | File | When to read|
298
- |---|---|
299
- | `references/widget-meta-directives.md` | For `forEach` / `forItem` (iteration) and `if` (conditional rendering), including nested loops|
300
- | `references/schema-from-lightning-type.md` | When `lightningTypeSchema` is provided; how to derive the widget `schema.json` from an Apex-backed Lightning Type|
301
- | `references/widget-bundle-layout.md` | Folder layout, `-meta.xml` shape, `<pkgDir>` resolution rules|
266
+ | File | When to read |
267
+ |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
268
+ | `references/widget-meta-directives.md` | For `forEach` / `forItem` (iteration) and `if` (conditional rendering), including nested loops |
269
+ | `references/schema-from-lightning-type.md` | When `lightningTypeSchema` is provided; how to derive the widget `schema.json` from an Apex-backed Lightning Type |
270
+ | `references/widget-bundle-layout.md` | Folder layout, `-meta.xml` shape, `<pkgDir>` resolution rules |
302
271
  | `references/widget-bundle-validation.md` | Step 9 — the checks to run before reporting |
303
- | `references/widget-formulas.md` | Supported formula functions and operators|
304
- | `examples/single-object.json` | Single-object pattern (root binding via `{!$attrs.X}`, no iteration)|
272
+ | `references/widget-formulas.md` | Supported formula functions and operators, including locale format functions (`FORMATNUMBER`, `FORMATCURRENCY`, `FORMATDATE`, …) |
273
+ | `references/widget-labels.md` | Localization — `$Label` syntax, naming and reuse, allowed attributes, combining with data, creating labels, and the label report |
274
+ | `references/widget-actions.md` | Complete `tile/button` JSON for `action/sendMessage` and `action/openLink` |
275
+ | `references/widget-styling.md` | Variant, size, heading, and icon conventions |
276
+ | `references/widget-gotchas.md` | Known failure modes and their fixes |
277
+ | `examples/single-object.json` | Single-object pattern (root binding via `{!$attrs.X}`, no iteration) |
305
278
  | `examples/list-with-foreach.json` | Any list-iteration case — root-level collections, nested lists, and lists embedded inside a single-object widget (e.g. iterating a `List<InnerClass>` inside an outer Apex payload) |
306
- | `examples/conditional.json` | Conditional pattern (`if` on `meta`, including `if` + `forEach` together)|
307
- | `examples/formulas.json` | Formula usage — text/arithmetic/date transforms, `IF`/`AND`/`NOT` driving both a `tile/text` and `meta.if`|
279
+ | `examples/conditional.json` | Conditional pattern (`if` on `meta`, including `if` + `forEach` together) |
280
+ | `examples/formulas.json` | Formula usage — text/arithmetic/date transforms, locale format functions, `IF`/`AND`/`NOT` driving both a `tile/text` and `meta.if` |
281
+ | `examples/localized-labels.json` | Localization — `$Label` in a heading, caption, badge (`IF` choosing between labels), button label, and a label joined with data |
@@ -138,6 +138,23 @@
138
138
 
139
139
  { "definition": "tile/spacer", "attributes": { "size": "sm" } },
140
140
 
141
+ {
142
+ "definition": "tile/column",
143
+ "attributes": { "gap": "xs" },
144
+ "children": [
145
+ { "definition": "tile/text", "attributes": { "text": "9. Locale format functions", "variant": "h3" } },
146
+ { "definition": "tile/text", "attributes": { "text": "FORMATNUMBER: {!FORMATNUMBER($attrs.price)} | precision 0: {!FORMATNUMBER($attrs.price, 0)}" } },
147
+ { "definition": "tile/text", "attributes": { "text": "FORMATCURRENCY: {!FORMATCURRENCY($attrs.price)}" } },
148
+ { "definition": "tile/text", "attributes": { "text": "FORMATPERCENT: {!FORMATPERCENT($attrs.winProbability)} | precision 1: {!FORMATPERCENT($attrs.winProbability, 1)}" } },
149
+ { "definition": "tile/text", "attributes": { "text": "FORMATDATE: {!FORMATDATE($attrs.departureDate)} | long: {!FORMATDATE($attrs.departureDate, 'long')}" } },
150
+ { "definition": "tile/text", "attributes": { "text": "FORMATDATETIME: {!FORMATDATETIME($attrs.departureDateTime)} | medium: {!FORMATDATETIME($attrs.departureDateTime, 'medium')}" } },
151
+ { "definition": "tile/text", "attributes": { "text": "FORMATTIME: {!FORMATTIME($attrs.departureDateTime)} | long: {!FORMATTIME($attrs.departureDateTime, 'long')}" } },
152
+ { "definition": "tile/text", "attributes": { "text": "{!'Fare with tax: ' & FORMATCURRENCY($attrs.price * (1 + $attrs.taxRate))}", "variant": "h4" } }
153
+ ]
154
+ },
155
+
156
+ { "definition": "tile/spacer", "attributes": { "size": "sm" } },
157
+
141
158
  {
142
159
  "definition": "tile/column",
143
160
  "meta": { "if": "{!AND($attrs.isPriority, $attrs.price > 500)}" },
@@ -147,7 +164,7 @@
147
164
  "attributes": { "gap": "sm", "align": "center" },
148
165
  "children": [
149
166
  { "definition": "tile/icon", "attributes": { "name": "lightning", "color": "warning" } },
150
- { "definition": "tile/text", "attributes": { "text": "9. Formula-driven meta.if: priority fare over $500 — expedited handling" } }
167
+ { "definition": "tile/text", "attributes": { "text": "10. Formula-driven meta.if: priority fare over $500 — expedited handling" } }
151
168
  ]
152
169
  }
153
170
  ]
@@ -160,7 +177,7 @@
160
177
  "meta": { "forEach": "{!$attrs.flights}", "forItem": "$flight" },
161
178
  "attributes": { "gap": "xs" },
162
179
  "children": [
163
- { "definition": "tile/text", "attributes": { "text": "10. forEach + per-row formula", "variant": "h3" } },
180
+ { "definition": "tile/text", "attributes": { "text": "11. forEach + per-row formula", "variant": "h3" } },
164
181
  {
165
182
  "definition": "tile/row",
166
183
  "attributes": { "justify": "between", "align": "center" },