@salesforce/afv-skills 1.5.0 → 1.5.2

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 (68) hide show
  1. package/README.md +16 -415
  2. package/package.json +5 -3
  3. package/skills/building-ui-bundle-app/SKILL.md +325 -0
  4. package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
  5. package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
  6. package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
  7. package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
  8. package/skills/deploying-ui-bundle/SKILL.md +77 -0
  9. package/skills/generating-apex/CREDITS.md +30 -0
  10. package/skills/generating-apex/SKILL.md +399 -0
  11. package/skills/generating-apex/assets/abstract.cls +132 -0
  12. package/skills/generating-apex/assets/batch.cls +125 -0
  13. package/skills/generating-apex/assets/domain.cls +102 -0
  14. package/skills/generating-apex/assets/dto.cls +108 -0
  15. package/skills/generating-apex/assets/exception.cls +51 -0
  16. package/skills/generating-apex/assets/interface.cls +25 -0
  17. package/skills/generating-apex/assets/invocable.cls +115 -0
  18. package/skills/generating-apex/assets/queueable.cls +92 -0
  19. package/skills/generating-apex/assets/rest-resource.cls +300 -0
  20. package/skills/generating-apex/assets/schedulable.cls +75 -0
  21. package/skills/generating-apex/assets/selector.cls +92 -0
  22. package/skills/generating-apex/assets/service.cls +69 -0
  23. package/skills/generating-apex/assets/trigger.cls +45 -0
  24. package/skills/generating-apex/assets/utility.cls +97 -0
  25. package/skills/generating-apex/references/AccountDeduplicationBatch.cls +148 -0
  26. package/skills/generating-apex/references/AccountSelector.cls +193 -0
  27. package/skills/generating-apex/references/AccountService.cls +201 -0
  28. package/skills/generating-apex-test/CREDITS.md +30 -0
  29. package/skills/generating-apex-test/SKILL.md +199 -0
  30. package/skills/generating-apex-test/assets/test-class-template.cls +93 -0
  31. package/skills/generating-apex-test/assets/test-data-factory-template.cls +111 -0
  32. package/skills/generating-apex-test/references/assertion-patterns.md +108 -0
  33. package/skills/generating-apex-test/references/async-testing.md +193 -0
  34. package/skills/generating-apex-test/references/mocking-patterns.md +220 -0
  35. package/skills/generating-apex-test/references/test-data-factory.md +75 -0
  36. package/skills/generating-experience-react-site/SKILL.md +20 -9
  37. package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
  38. package/skills/generating-flexipage/SKILL.md +58 -60
  39. package/skills/generating-ui-bundle-features/SKILL.md +45 -0
  40. package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
  41. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
  42. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
  43. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
  44. package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
  45. package/skills/searching-media/SKILL.md +342 -0
  46. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
  47. package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
  48. package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
  49. package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
  50. package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
  51. package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
  52. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
  53. package/skills/building-webapp-data-visualization/SKILL.md +0 -72
  54. package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
  55. package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
  56. package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
  57. package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
  58. package/skills/building-webapp-react-components/SKILL.md +0 -96
  59. package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
  60. package/skills/configuring-webapp-metadata/SKILL.md +0 -158
  61. package/skills/creating-webapp/SKILL.md +0 -140
  62. package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
  63. package/skills/installing-webapp-features/SKILL.md +0 -210
  64. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
  65. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
  66. /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
  67. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
  68. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
@@ -0,0 +1,193 @@
1
+ # Async Testing Patterns
2
+
3
+ ## Key Principle
4
+
5
+ `Test.stopTest()` forces all async operations to execute synchronously, allowing assertions on their results.
6
+
7
+ ## Batch Apex Testing
8
+
9
+ ### Basic Batch Test
10
+
11
+ **Critical:** In test context only one `execute()` invocation runs, so always set `batchSize >= testRecordCount` (e.g., `Database.executeBatch(batch, 200)` with 200 records). Never create more records than the batch size.
12
+
13
+ ```apex
14
+ @isTest
15
+ static void shouldProcessAllRecords_WhenBatchExecutes() {
16
+ List<Account> accounts = TestDataFactory.createAccounts(200, true);
17
+
18
+ Test.startTest();
19
+ MyBatchClass batch = new MyBatchClass();
20
+ Id batchId = Database.executeBatch(batch, 200);
21
+ Test.stopTest();
22
+
23
+ List<Account> updated = [SELECT Id, Status__c FROM Account];
24
+ for (Account acc : updated) {
25
+ Assert.areEqual('Processed', acc.Status__c,
26
+ 'Batch should update all account statuses');
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### Batch with Failures
32
+
33
+ ```apex
34
+ @isTest
35
+ static void shouldLogErrors_WhenRecordsFail() {
36
+ List<Account> accounts = TestDataFactory.createAccounts(198, true);
37
+
38
+ List<Account> invalidAccounts = new List<Account>();
39
+ for (Integer i = 0; i < 2; i++) {
40
+ invalidAccounts.add(new Account(
41
+ Name = 'Invalid Account ' + i,
42
+ Invalid_Field__c = 'triggers_validation_error'
43
+ ));
44
+ }
45
+ insert invalidAccounts;
46
+
47
+ Test.startTest();
48
+ MyBatchClass batch = new MyBatchClass();
49
+ Database.executeBatch(batch, 200);
50
+ Test.stopTest();
51
+
52
+ List<Error_Log__c> errors = [SELECT Id, Message__c FROM Error_Log__c];
53
+ Assert.areEqual(2, errors.size(), 'Should log 2 failed records');
54
+ }
55
+ ```
56
+
57
+ ### Batch Chaining
58
+
59
+ Test `finish()` chaining in a **separate test method** — calling `Database.executeBatch()` inside `finish()` during a test can throw `UnexpectedException`. Verify the first batch independently, then test that `finish()` enqueues the next batch.
60
+
61
+ ## Queueable Testing
62
+
63
+ ### Basic Queueable Test
64
+
65
+ ```apex
66
+ @isTest
67
+ static void shouldCompleteProcessing_WhenQueueableEnqueued() {
68
+ Account acc = TestDataFactory.createAccount(true);
69
+
70
+ Test.startTest();
71
+ MyQueueableClass queueable = new MyQueueableClass(acc.Id);
72
+ System.enqueueJob(queueable);
73
+ Test.stopTest();
74
+
75
+ Account updated = [SELECT Id, Status__c FROM Account WHERE Id = :acc.Id];
76
+ Assert.areEqual('Processed', updated.Status__c,
77
+ 'Queueable should update account status');
78
+ }
79
+ ```
80
+
81
+ ### Queueable Chaining
82
+
83
+ Only the **first** chained queueable executes in tests. Design tests to verify:
84
+ 1. First job completes correctly
85
+ 2. Chain is properly enqueued (query `AsyncApexJob`)
86
+ 3. Each job works independently in its own test method
87
+
88
+ ```apex
89
+ @isTest
90
+ static void shouldChainNextJob_WhenMoreRecordsExist() {
91
+ List<Account> accounts = TestDataFactory.createAccounts(500, true);
92
+
93
+ Test.startTest();
94
+ MyChainedQueueable queueable = new MyChainedQueueable(0, 100);
95
+ System.enqueueJob(queueable);
96
+ Test.stopTest();
97
+
98
+ List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
99
+ Assert.areEqual(100, processed.size(), 'First batch should process 100 records');
100
+
101
+ List<AsyncApexJob> jobs = [
102
+ SELECT Id, Status, JobType
103
+ FROM AsyncApexJob
104
+ WHERE ApexClass.Name = 'MyChainedQueueable'
105
+ ];
106
+ Assert.isTrue(jobs.size() >= 1, 'Chained job should be enqueued');
107
+ }
108
+ ```
109
+
110
+ ### Queueable with Callouts
111
+
112
+ Set the callout mock **before** `Test.startTest()`:
113
+
114
+ ```apex
115
+ @isTest
116
+ static void shouldMakeCallout_WhenQueueableWithCallout() {
117
+ Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, '{"status":"ok"}'));
118
+ Account acc = TestDataFactory.createAccount(true);
119
+
120
+ Test.startTest();
121
+ MyQueueableWithCallout queueable = new MyQueueableWithCallout(acc.Id);
122
+ System.enqueueJob(queueable);
123
+ Test.stopTest();
124
+
125
+ Account updated = [SELECT Id, External_Status__c FROM Account WHERE Id = :acc.Id];
126
+ Assert.areEqual('Synced', updated.External_Status__c,
127
+ 'Should update status after successful callout');
128
+ }
129
+ ```
130
+
131
+ ## Future Method Testing
132
+
133
+ ```apex
134
+ @isTest
135
+ static void shouldExecuteFutureMethod() {
136
+ Account acc = TestDataFactory.createAccount(true);
137
+
138
+ Test.startTest();
139
+ MyClass.processFuture(acc.Id);
140
+ Test.stopTest();
141
+
142
+ Account updated = [SELECT Id, Processed__c FROM Account WHERE Id = :acc.Id];
143
+ Assert.areEqual(true, updated.Processed__c, 'Future should process record');
144
+ }
145
+ ```
146
+
147
+ ## Scheduled Apex Testing
148
+
149
+ ### Direct Execution
150
+
151
+ ```apex
152
+ @isTest
153
+ static void shouldExecuteScheduledJob() {
154
+ List<Account> accounts = TestDataFactory.createAccounts(50, true);
155
+
156
+ Test.startTest();
157
+ MyScheduledClass scheduled = new MyScheduledClass();
158
+ scheduled.execute(null);
159
+ Test.stopTest();
160
+
161
+ List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
162
+ Assert.areEqual(50, processed.size(), 'Scheduled job should process records');
163
+ }
164
+ ```
165
+
166
+ ### CRON Registration
167
+
168
+ ```apex
169
+ @isTest
170
+ static void shouldScheduleJob() {
171
+ Test.startTest();
172
+ String cronExp = '0 0 6 * * ?';
173
+ String jobId = System.schedule('Daily Processing', cronExp, new MyScheduledClass());
174
+ Test.stopTest();
175
+
176
+ CronTrigger ct = [
177
+ SELECT Id, CronExpression, State
178
+ FROM CronTrigger
179
+ WHERE Id = :jobId
180
+ ];
181
+ Assert.areEqual('0 0 6 * * ?', ct.CronExpression, 'CRON should match');
182
+ Assert.areEqual('WAITING', ct.State, 'Job should be waiting');
183
+ }
184
+ ```
185
+
186
+ ## Common Pitfalls
187
+
188
+ | Pitfall | Impact |
189
+ |---|---|
190
+ | Missing `Test.stopTest()` | Async never executes, assertions fail silently |
191
+ | Expecting all chained queueables to run | Only the first runs; test each independently |
192
+ | Mock set after `Test.startTest()` | Callout mock must be set **before** `Test.startTest()` |
193
+ | Batch size < record count in tests | Only `batchSize` records processed; set `batchSize >= recordCount` |
@@ -0,0 +1,220 @@
1
+ # Mocking Patterns
2
+
3
+ ## HTTP Callout Mocking
4
+
5
+ Apex doesn't allow real HTTP callouts in tests. Use `HttpCalloutMock` interface.
6
+
7
+ ### Basic Mock
8
+
9
+ ```apex
10
+ @isTest
11
+ public class MockHttpResponse implements HttpCalloutMock {
12
+
13
+ private Integer statusCode;
14
+ private String body;
15
+
16
+ public MockHttpResponse(Integer statusCode, String body) {
17
+ this.statusCode = statusCode;
18
+ this.body = body;
19
+ }
20
+
21
+ public HTTPResponse respond(HTTPRequest req) {
22
+ HttpResponse res = new HttpResponse();
23
+ res.setStatusCode(statusCode);
24
+ res.setBody(body);
25
+ res.setHeader('Content-Type', 'application/json');
26
+ return res;
27
+ }
28
+ }
29
+ ```
30
+
31
+ ### Using the Mock
32
+
33
+ ```apex
34
+ @isTest
35
+ static void shouldProcessApiResponse_WhenCalloutSucceeds() {
36
+ String mockResponse = '{"status": "success", "data": [{"id": "123"}]}';
37
+ Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, mockResponse));
38
+
39
+ Test.startTest();
40
+ List<ExternalRecord> results = MyIntegrationService.fetchRecords();
41
+ Test.stopTest();
42
+
43
+ Assert.areEqual(1, results.size(), 'Should parse one record from response');
44
+ Assert.areEqual('123', results[0].externalId, 'Should extract correct ID');
45
+ }
46
+
47
+ @isTest
48
+ static void shouldHandleError_WhenCalloutFails() {
49
+ String errorResponse = '{"error": "Unauthorized"}';
50
+ Test.setMock(HttpCalloutMock.class, new MockHttpResponse(401, errorResponse));
51
+
52
+ Test.startTest();
53
+ CalloutResult result = MyIntegrationService.fetchRecords();
54
+ Test.stopTest();
55
+
56
+ Assert.areEqual(false, result.isSuccess, 'Should indicate failure');
57
+ Assert.isTrue(result.errorMessage.contains('Unauthorized'), 'Should capture error');
58
+ }
59
+ ```
60
+
61
+ ### Multi-Request Mock
62
+
63
+ For services making multiple callouts:
64
+
65
+ ```apex
66
+ @isTest
67
+ public class MultiRequestMock implements HttpCalloutMock {
68
+
69
+ private Map<String, HttpResponse> endpointResponses;
70
+
71
+ public MultiRequestMock(Map<String, HttpResponse> responses) {
72
+ this.endpointResponses = responses;
73
+ }
74
+
75
+ public HTTPResponse respond(HTTPRequest req) {
76
+ String endpoint = req.getEndpoint();
77
+ for (String key : endpointResponses.keySet()) {
78
+ if (endpoint.contains(key)) {
79
+ return endpointResponses.get(key);
80
+ }
81
+ }
82
+ HttpResponse res = new HttpResponse();
83
+ res.setStatusCode(404);
84
+ res.setBody('{"error": "Not found"}');
85
+ return res;
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### StaticResourceCalloutMock
91
+
92
+ Use when response JSON is large or complex:
93
+
94
+ ```apex
95
+ @isTest
96
+ static void shouldParseComplexResponse() {
97
+ StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
98
+ mock.setStaticResource('TestApiResponse');
99
+ mock.setStatusCode(200);
100
+ mock.setHeader('Content-Type', 'application/json');
101
+ Test.setMock(HttpCalloutMock.class, mock);
102
+
103
+ Test.startTest();
104
+ Result r = MyService.callExternalApi();
105
+ Test.stopTest();
106
+
107
+ Assert.isNotNull(r, 'Should parse response');
108
+ }
109
+ ```
110
+
111
+ ## SOSL Mocking
112
+
113
+ SOSL returns empty results in tests by default. Call `Test.setFixedSearchResults(List<Id>)` before the search:
114
+
115
+ ```apex
116
+ @isTest
117
+ static void shouldReturnSearchResults() {
118
+ Account acc = TestDataFactory.createAccount(true);
119
+ Test.setFixedSearchResults(new List<Id>{ acc.Id });
120
+
121
+ Test.startTest();
122
+ List<Account> results = MyService.searchAccounts('Test');
123
+ Test.stopTest();
124
+
125
+ Assert.areEqual(1, results.size(), 'Should return mocked search result');
126
+ }
127
+ ```
128
+
129
+ ## DML Mocking (Constructor Injection)
130
+
131
+ Design for testability — use a public constructor for production and a `@TestVisible private` constructor that accepts mock interfaces:
132
+
133
+ ```apex
134
+ public class MyService {
135
+ private IDML dmlHandler;
136
+
137
+ public MyService() {
138
+ this.dmlHandler = new DMLHandler();
139
+ }
140
+
141
+ @TestVisible
142
+ private MyService(IDML dmlHandler) {
143
+ this.dmlHandler = dmlHandler;
144
+ }
145
+
146
+ public void createRecords(List<Account> accounts) {
147
+ dmlHandler.doInsert(accounts);
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Stub API (System.StubProvider)
153
+
154
+ For mocking Apex class dependencies:
155
+
156
+ ```apex
157
+ @isTest
158
+ public class MyServiceMock implements System.StubProvider {
159
+
160
+ public Object handleMethodCall(
161
+ Object stubbedObject, String stubbedMethodName, Type returnType,
162
+ List<Type> paramTypes, List<String> paramNames, List<Object> args
163
+ ) {
164
+ if (stubbedMethodName == 'getAccountData') {
165
+ return new AccountData('Mock Account', 'Active');
166
+ }
167
+ return null;
168
+ }
169
+ }
170
+
171
+ @isTest
172
+ static void shouldUseAccountData() {
173
+ MyServiceMock mockProvider = new MyServiceMock();
174
+ IMyService mockService = (IMyService) Test.createStub(IMyService.class, mockProvider);
175
+
176
+ MyController controller = new MyController(mockService);
177
+
178
+ Test.startTest();
179
+ String result = controller.displayAccountInfo();
180
+ Test.stopTest();
181
+
182
+ Assert.isTrue(result.contains('Mock Account'), 'Should use mocked data');
183
+ }
184
+ ```
185
+
186
+ ## Email Testing
187
+
188
+ Apex doesn't actually send emails in tests. Use limits to verify:
189
+
190
+ ```apex
191
+ @isTest
192
+ static void shouldSendEmail_WhenTriggered() {
193
+ Integer emailsBefore = Limits.getEmailInvocations();
194
+
195
+ Test.startTest();
196
+ MyService.sendNotification(testContact);
197
+ Test.stopTest();
198
+
199
+ Assert.areEqual(emailsBefore + 1, Limits.getEmailInvocations(),
200
+ 'One email should be sent');
201
+ }
202
+ ```
203
+
204
+ ## Platform Event Testing
205
+
206
+ ```apex
207
+ @isTest
208
+ static void shouldPublishEvent_WhenRecordCreated() {
209
+ Test.startTest();
210
+ Test.enableChangeDataCapture();
211
+
212
+ Account acc = TestDataFactory.createAccount(true);
213
+ Test.getEventBus().deliver();
214
+ Test.stopTest();
215
+
216
+ // Query platform event trigger results
217
+ List<EventLog__c> logs = [SELECT Id FROM EventLog__c WHERE AccountId__c = :acc.Id];
218
+ Assert.areEqual(1, logs.size(), 'Event handler should create log record');
219
+ }
220
+ ```
@@ -0,0 +1,75 @@
1
+ # TestDataFactory Patterns
2
+
3
+ For the base class template, see [assets/test-data-factory-template.cls](../assets/test-data-factory-template.cls).
4
+
5
+ ## Design Rules
6
+
7
+ 1. **Always accept a `doInsert` flag** — lets callers modify records before insert
8
+ 2. **Append loop index to all fields that participate in matching rules** — prevents `DUPLICATES_DETECTED` errors from active Duplicate Rules
9
+ 3. **Single-record methods delegate to bulk** — e.g., `createAccount(doInsert)` calls `createAccounts(1, doInsert)[0]`
10
+ 4. **Return created records** — enables chaining and further manipulation
11
+ 5. **Set all required fields** — include fields enforced by validation rules, not just schema-required fields
12
+
13
+ ## Field Override Pattern
14
+
15
+ Allow callers to override default values without creating new factory methods:
16
+
17
+ ```apex
18
+ public static Account createAccount(Map<String, Object> fieldOverrides, Boolean doInsert) {
19
+ Account acc = new Account(
20
+ Name = 'Test Account',
21
+ Industry = 'Technology'
22
+ );
23
+ for (String fieldName : fieldOverrides.keySet()) {
24
+ acc.put(fieldName, fieldOverrides.get(fieldName));
25
+ }
26
+ if (doInsert) insert acc;
27
+ return acc;
28
+ }
29
+
30
+ // Usage:
31
+ Account acc = TestDataFactory.createAccount(new Map<String, Object>{
32
+ 'Name' => 'Custom Name',
33
+ 'Industry' => 'Healthcare'
34
+ }, true);
35
+ ```
36
+
37
+ ## Record Type Support
38
+
39
+ ```apex
40
+ public static Account createAccountByRecordType(String recordTypeName, Boolean doInsert) {
41
+ Id recordTypeId = Schema.SObjectType.Account
42
+ .getRecordTypeInfosByDeveloperName()
43
+ .get(recordTypeName)
44
+ .getRecordTypeId();
45
+
46
+ Account acc = new Account(
47
+ Name = 'Test Account',
48
+ RecordTypeId = recordTypeId
49
+ );
50
+ if (doInsert) insert acc;
51
+ return acc;
52
+ }
53
+ ```
54
+
55
+ ## Handling Duplicate Rules
56
+
57
+ When unique field values alone are not sufficient, use `Database.insert()` with a `DuplicateRuleHeader`:
58
+
59
+ ```apex
60
+ public static List<Account> createAccountsAllowDuplicates(Integer count, Boolean doInsert) {
61
+ List<Account> accounts = new List<Account>();
62
+ for (Integer i = 0; i < count; i++) {
63
+ accounts.add(new Account(
64
+ Name = 'Test Account ' + i,
65
+ Phone = '555-000-' + String.valueOf(i).leftPad(4, '0')
66
+ ));
67
+ }
68
+ if (doInsert) {
69
+ Database.DMLOptions dml = new Database.DMLOptions();
70
+ dml.DuplicateRuleHeader.allowSave = true;
71
+ Database.insert(accounts, dml);
72
+ }
73
+ return accounts;
74
+ }
75
+ ```
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: generating-experience-react-site
3
- description: "Use this skill when users need to create or configure a Salesforce Digital Experience Site specifically for hosting a React web application. Trigger when users mention creating an Experience site for a React app, setting up a React site on Salesforce, configuring Network/CustomSite/DigitalExperience metadata for a web app, or deploying site infrastructure for a React application. Also trigger when users mention site URL path prefixes, app namespaces, appDevName, guest access configuration, DigitalExperienceConfig, DigitalExperienceBundle, or sfdc_cms__site content types in the context of React apps. Always use this skill for any React web application site creation or site infrastructure configuration work, even if the user just says \"create a site for my React app\" or \"set up the site for my web application.\""
3
+ description: "Use this skill when users need to create or configure a Salesforce Digital Experience Site specifically for hosting a React UI bundle. Trigger when users mention creating an Experience site for a React app, setting up a React site on Salesforce, configuring Network/CustomSite/DigitalExperience metadata for a UI bundle, or deploying site infrastructure for a React application. Also trigger when users mention site URL path prefixes, app namespaces, appDevName, guest access configuration, DigitalExperienceConfig, DigitalExperienceBundle, or sfdc_cms__site content types in the context of React apps. Always use this skill for any React UI bundle site creation or site infrastructure configuration work, even if the user just says \"create a site for my React app\" or \"set up the site for my UI bundle.\""
4
4
  ---
5
5
 
6
- # Digital Experience Site for React Web Applications
7
- Create and configure Digital Experience Sites that host React web applications on Salesforce. This skill generates the minimum necessary site infrastructure — Network, CustomSite, DigitalExperienceConfig, DigitalExperienceBundle, and the `sfdc_cms__site` content type — so a React app can be served from Salesforce.
6
+ # Digital Experience Site for React UI Bundles
7
+ Create and configure Digital Experience Sites that host React UI bundles on Salesforce. This skill generates the minimum necessary site infrastructure — Network, CustomSite, DigitalExperienceConfig, DigitalExperienceBundle, and the `sfdc_cms__site` content type — so a React app can be served from Salesforce.
8
8
 
9
- React sites differ from standard LWR sites: they don't need routes, views, theme layouts, or branding sets. The site acts as a thin container (`appContainer: true`) that delegates rendering to the React application referenced by `appSpace`.
9
+ React sites differ from standard LWR sites: they don't need routes, views, theme layouts, or branding sets. The site acts as a thin container (`appContainer: true`) that delegates rendering to the React UI bundle referenced by `appSpace`.
10
10
 
11
11
  ## Required Properties
12
12
  Resolve all five properties before generating any metadata. Each has a fallback chain — work through each option in order until a value is found.
@@ -14,9 +14,9 @@ Resolve all five properties before generating any metadata. Each has a fallback
14
14
  | Property | Format | How to Resolve |
15
15
  |----------|--------|----------------|
16
16
  | **siteName** | `UpperCamelCase` (e.g., `MyCommunity`) | Ask user or derive from context |
17
- | **siteUrlPathPrefix** | `kebab-case` (e.g., `my-community`) | User-provided, or convert siteName to kebab-case |
17
+ | **siteUrlPathPrefix** | `All lowercase` (e.g., `mycommunity`) | User-provided, or convert siteName to all lowercase with alphanumeric characters only |
18
18
  | **appNamespace** | String | `namespace` in `sfdx-project.json` → `sf data query -q "SELECT NamespacePrefix FROM Organization" --target-org ${usernameOrAlias}` → default `c` |
19
- | **appDevName** | String | `webApplication` metadata in the project → `sf data query -q "SELECT DeveloperName FROM WebApplication" --target-org ${usernameOrAlias}` → default to siteName |
19
+ | **appDevName** | String | `UIBundle` metadata in the project → `sf data query -q "SELECT DeveloperName FROM UIBundle" --target-org ${usernameOrAlias}` → default to siteName |
20
20
  | **enableGuestAccess** | Boolean | Ask user whether unauthenticated guest users can access site APIs → default `false` |
21
21
 
22
22
  The `appNamespace` and `appDevName` properties connect the site to the correct React application. Getting these wrong means the site deploys but shows a blank page, so take care to resolve them from real project data.
@@ -26,7 +26,7 @@ The `appNamespace` and `appDevName` properties connect the site to the correct R
26
26
  Determine values for all five properties before constructing anything. Use the resolution strategies in the table above, falling through each option until a value is found.
27
27
 
28
28
  ### Step 2: Create the Project Structure
29
- Call the `get_metadata_api_context` MCP tool to retrieve schemas for `Network`, `CustomSite`, `DigitalExperienceConfig`, and `DigitalExperienceBundle` metadata types. These schemas define the valid XML structure for each file.
29
+ Use available Salesforce metadata schema and field context for `Network`, `CustomSite`, `DigitalExperienceConfig`, and `DigitalExperienceBundle` to ensure each file uses valid structure.
30
30
 
31
31
  Create any files and directories that don't already exist, using these paths:
32
32
 
@@ -51,8 +51,19 @@ Use the default templates in the docs below. Values in `{braces}` are resolved p
51
51
  | DigitalExperienceBundle | [configure-metadata-digital-experience-bundle.md](docs/configure-metadata-digital-experience-bundle.md) |
52
52
  | DigitalExperience (sfdc_cms__site) | [configure-metadata-digital-experience.md](docs/configure-metadata-digital-experience.md) |
53
53
 
54
+ ### Execution Note for Step 3: Load and use the docs
55
+ - Agents MUST read the full contents of each docs/*.md file referenced in Step 3 before attempting to populate metadata fields.
56
+ - Use your platform's file-read tool (for example, `read_file`) to load these files in full, then perform placeholder substitution for values in `{braces}` using the resolved properties from Step 1.
57
+ - Files to load:
58
+ - `docs/configure-metadata-network.md`
59
+ - `docs/configure-metadata-custom-site.md`
60
+ - `docs/configure-metadata-digital-experience-config.md`
61
+ - `docs/configure-metadata-digital-experience-bundle.md`
62
+ - `docs/configure-metadata-digital-experience.md`
63
+ - Read entire file contents, replace placeholders (e.g. `{siteName}`) with the resolved values, then use the expanded templates to populate the metadata XML/JSON content.
64
+
54
65
  ### Step 4: Resolve Additional Configurations
55
- Address any extra configurations the user requests. Use the schemas returned by `get_metadata_api_context` in Step 2 to understand each field's purpose, and update only the minimum necessary fields.
66
+ Address any extra configurations the user requests. Use the metadata sections and field context identified in Step 2 to understand each fields purpose and constraints, then update only the minimum necessary fields.
56
67
 
57
68
  ## Verification Checklist
58
69
  Before deploying, confirm:
@@ -60,7 +71,7 @@ Before deploying, confirm:
60
71
  - [ ] All five required properties are resolved
61
72
  - [ ] All metadata directories and files exist per the project structure
62
73
  - [ ] All metadata fields are populated per the templates and user requests
63
- - [ ] `appSpace` in `content.json` matches an existing `WebApplication` metadata record
74
+ - [ ] `appSpace` in `content.json` matches an existing `UIBundle` metadata record
64
75
  - [ ] Deployment validates successfully:
65
76
  ```bash
66
77
  sf project deploy validate --metadata Network CustomSite DigitalExperienceConfig DigitalExperienceBundle DigitalExperience --target-org ${usernameOrAlias}
@@ -3,7 +3,7 @@
3
3
  ## Purpose
4
4
  These configuration files create **net-new, default** DigitalExperience content records (`sfdc_cms__site` type) for a Digital Experience React Site. They are not intended to edit or modify existing DigitalExperience content. Use these templates only when provisioning a brand-new React site.
5
5
 
6
- The `appContainer: true` and `appSpace` fields in `content.json` are what make this a React site rather than a standard LWR site. The `appSpace` value follows the format `{namespace}__{developerName}` and must match a deployed `WebApplication` metadata record.
6
+ The `appContainer: true` and `appSpace` fields in `content.json` are what make this a React site rather than a standard LWR site. The `appSpace` value follows the format `{namespace}__{developerName}` and must match a deployed `UIBundle` metadata record.
7
7
 
8
8
  ## File Location
9
9
  The DigitalExperience directory contains only `_meta.json` and `content.json`. Do not create any directories other than `sfdc_cms__site` inside the bundle.