@salesforce/afv-skills 1.5.0 → 1.5.1

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 (27) hide show
  1. package/package.json +3 -3
  2. package/skills/creating-webapp/SKILL.md +0 -2
  3. package/skills/generating-apex/SKILL.md +253 -0
  4. package/skills/generating-apex/assets/abstract.cls +128 -0
  5. package/skills/generating-apex/assets/batch.cls +125 -0
  6. package/skills/generating-apex/assets/domain.cls +102 -0
  7. package/skills/generating-apex/assets/dto.cls +108 -0
  8. package/skills/generating-apex/assets/exception.cls +51 -0
  9. package/skills/generating-apex/assets/interface.cls +25 -0
  10. package/skills/generating-apex/assets/queueable.cls +92 -0
  11. package/skills/generating-apex/assets/schedulable.cls +75 -0
  12. package/skills/generating-apex/assets/selector.cls +92 -0
  13. package/skills/generating-apex/assets/service.cls +69 -0
  14. package/skills/generating-apex/assets/utility.cls +97 -0
  15. package/skills/generating-apex/references/AccountDeduplicationBatch.cls +148 -0
  16. package/skills/generating-apex/references/AccountSelector.cls +193 -0
  17. package/skills/generating-apex/references/AccountService.cls +201 -0
  18. package/skills/generating-apex-test/SKILL.md +108 -0
  19. package/skills/generating-apex-test/assets/test-class-template.cls +124 -0
  20. package/skills/generating-apex-test/assets/test-data-factory-template.cls +112 -0
  21. package/skills/generating-apex-test/references/assertion-patterns.md +165 -0
  22. package/skills/generating-apex-test/references/async-testing.md +276 -0
  23. package/skills/generating-apex-test/references/mocking-patterns.md +219 -0
  24. package/skills/generating-apex-test/references/test-data-factory.md +176 -0
  25. package/skills/generating-experience-react-site/SKILL.md +11 -0
  26. package/skills/generating-flexipage/SKILL.md +39 -57
  27. package/skills/searching-media/SKILL.md +342 -0
@@ -0,0 +1,219 @@
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 Implementation
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
+ // Given
37
+ String mockResponse = '{"status": "success", "data": [{"id": "123"}]}';
38
+ Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, mockResponse));
39
+
40
+ // When
41
+ Test.startTest();
42
+ List<ExternalRecord> results = MyIntegrationService.fetchRecords();
43
+ Test.stopTest();
44
+
45
+ // Then
46
+ System.assertEquals(1, results.size(), 'Should parse one record from response');
47
+ System.assertEquals('123', results[0].externalId, 'Should extract correct ID');
48
+ }
49
+
50
+ @isTest
51
+ static void shouldHandleError_WhenCalloutFails() {
52
+ // Given
53
+ String errorResponse = '{"error": "Unauthorized"}';
54
+ Test.setMock(HttpCalloutMock.class, new MockHttpResponse(401, errorResponse));
55
+
56
+ // When
57
+ Test.startTest();
58
+ CalloutResult result = MyIntegrationService.fetchRecords();
59
+ Test.stopTest();
60
+
61
+ // Then
62
+ System.assertEquals(false, result.isSuccess, 'Should indicate failure');
63
+ System.assert(result.errorMessage.contains('Unauthorized'), 'Should capture error');
64
+ }
65
+ ```
66
+
67
+ ### Multi-Request Mock
68
+
69
+ For services making multiple callouts:
70
+
71
+ ```apex
72
+ @isTest
73
+ public class MultiRequestMock implements HttpCalloutMock {
74
+
75
+ private Map<String, HttpResponse> endpointResponses;
76
+
77
+ public MultiRequestMock(Map<String, HttpResponse> responses) {
78
+ this.endpointResponses = responses;
79
+ }
80
+
81
+ public HTTPResponse respond(HTTPRequest req) {
82
+ String endpoint = req.getEndpoint();
83
+
84
+ for (String key : endpointResponses.keySet()) {
85
+ if (endpoint.contains(key)) {
86
+ return endpointResponses.get(key);
87
+ }
88
+ }
89
+
90
+ // Default 404 if no match
91
+ HttpResponse res = new HttpResponse();
92
+ res.setStatusCode(404);
93
+ res.setBody('{"error": "Not found"}');
94
+ return res;
95
+ }
96
+ }
97
+
98
+ // Usage:
99
+ Map<String, HttpResponse> mocks = new Map<String, HttpResponse>();
100
+
101
+ HttpResponse authResponse = new HttpResponse();
102
+ authResponse.setStatusCode(200);
103
+ authResponse.setBody('{"token": "abc123"}');
104
+ mocks.put('/oauth/token', authResponse);
105
+
106
+ HttpResponse dataResponse = new HttpResponse();
107
+ dataResponse.setStatusCode(200);
108
+ dataResponse.setBody('{"records": []}');
109
+ mocks.put('/api/records', dataResponse);
110
+
111
+ Test.setMock(HttpCalloutMock.class, new MultiRequestMock(mocks));
112
+ ```
113
+
114
+ ## StaticResourceCalloutMock
115
+
116
+ For complex response bodies, store JSON in Static Resources:
117
+
118
+ ```apex
119
+ @isTest
120
+ static void shouldParseComplexResponse() {
121
+ StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
122
+ mock.setStaticResource('TestApiResponse'); // Static Resource name
123
+ mock.setStatusCode(200);
124
+ mock.setHeader('Content-Type', 'application/json');
125
+
126
+ Test.setMock(HttpCalloutMock.class, mock);
127
+
128
+ Test.startTest();
129
+ Result r = MyService.callExternalApi();
130
+ Test.stopTest();
131
+
132
+ System.assertNotEquals(null, r, 'Should parse response');
133
+ }
134
+ ```
135
+
136
+ ## Stub API (Enterprise Pattern)
137
+
138
+ For mocking Apex class dependencies using `System.StubProvider`:
139
+
140
+ ```apex
141
+ @isTest
142
+ public class MyServiceMock implements System.StubProvider {
143
+
144
+ public Object handleMethodCall(
145
+ Object stubbedObject,
146
+ String stubbedMethodName,
147
+ Type returnType,
148
+ List<Type> paramTypes,
149
+ List<String> paramNames,
150
+ List<Object> args
151
+ ) {
152
+ if (stubbedMethodName == 'getAccountData') {
153
+ return new AccountData('Mock Account', 'Active');
154
+ }
155
+ return null;
156
+ }
157
+ }
158
+
159
+ // Usage in test:
160
+ @isTest
161
+ static void shouldUseAccountData() {
162
+ MyServiceMock mockProvider = new MyServiceMock();
163
+ IMyService mockService = (IMyService)Test.createStub(IMyService.class, mockProvider);
164
+
165
+ // Inject mock into class under test
166
+ MyController controller = new MyController(mockService);
167
+
168
+ Test.startTest();
169
+ String result = controller.displayAccountInfo();
170
+ Test.stopTest();
171
+
172
+ System.assert(result.contains('Mock Account'), 'Should use mocked data');
173
+ }
174
+ ```
175
+
176
+ ## Email Mocking
177
+
178
+ Apex sends real emails by default. Use limits to verify:
179
+
180
+ ```apex
181
+ @isTest
182
+ static void shouldSendEmail_WhenTriggered() {
183
+ Integer emailsBefore = Limits.getEmailInvocations();
184
+
185
+ Test.startTest();
186
+ MyService.sendNotification(testContact);
187
+ Test.stopTest();
188
+
189
+ // Verify email was queued (not actually sent in tests)
190
+ System.assertEquals(
191
+ emailsBefore + 1,
192
+ Limits.getEmailInvocations(),
193
+ 'One email should be sent'
194
+ );
195
+ }
196
+ ```
197
+
198
+ ## Platform Event Testing
199
+
200
+ ```apex
201
+ @isTest
202
+ static void shouldPublishEvent_WhenRecordCreated() {
203
+ Test.startTest();
204
+
205
+ // Enable event delivery in test context
206
+ Test.enableChangeDataCapture();
207
+
208
+ Account acc = TestDataFactory.createAccount(true);
209
+
210
+ // Deliver events
211
+ Test.getEventBus().deliver();
212
+
213
+ Test.stopTest();
214
+
215
+ // Query platform event trigger results
216
+ List<EventLog__c> logs = [SELECT Id FROM EventLog__c WHERE AccountId__c = :acc.Id];
217
+ System.assertEquals(1, logs.size(), 'Event handler should create log record');
218
+ }
219
+ ```
@@ -0,0 +1,176 @@
1
+ # TestDataFactory Patterns
2
+
3
+ ## Overview
4
+
5
+ TestDataFactory is a centralized utility class for creating test records with sensible defaults. It ensures consistent test data across all test classes and reduces duplication.
6
+
7
+ ## Base Template
8
+
9
+ ```apex
10
+ @isTest
11
+ public class TestDataFactory {
12
+
13
+ // ============ ACCOUNTS ============
14
+
15
+ public static List<Account> createAccounts(Integer count, Boolean doInsert) {
16
+ List<Account> accounts = new List<Account>();
17
+ for (Integer i = 0; i < count; i++) {
18
+ accounts.add(new Account(
19
+ Name = 'Test Account ' + i,
20
+ BillingStreet = '123 Test St',
21
+ BillingCity = 'San Francisco',
22
+ BillingState = 'CA',
23
+ BillingPostalCode = '94105',
24
+ BillingCountry = 'USA',
25
+ Industry = 'Technology',
26
+ Type = 'Customer'
27
+ ));
28
+ }
29
+ if (doInsert) insert accounts;
30
+ return accounts;
31
+ }
32
+
33
+ public static Account createAccount(Boolean doInsert) {
34
+ return createAccounts(1, doInsert)[0];
35
+ }
36
+
37
+ // ============ CONTACTS ============
38
+
39
+ public static List<Contact> createContacts(List<Account> accounts, Integer countPerAccount, Boolean doInsert) {
40
+ List<Contact> contacts = new List<Contact>();
41
+ Integer index = 0;
42
+ for (Account acc : accounts) {
43
+ for (Integer i = 0; i < countPerAccount; i++) {
44
+ contacts.add(new Contact(
45
+ FirstName = 'Test',
46
+ LastName = 'Contact ' + index,
47
+ Email = 'test.contact' + index + '@example.com',
48
+ Phone = '555-000-' + String.valueOf(index).leftPad(4, '0'),
49
+ AccountId = acc.Id
50
+ ));
51
+ index++;
52
+ }
53
+ }
54
+ if (doInsert) insert contacts;
55
+ return contacts;
56
+ }
57
+
58
+ // ============ OPPORTUNITIES ============
59
+
60
+ public static List<Opportunity> createOpportunities(List<Account> accounts, Integer countPerAccount, Boolean doInsert) {
61
+ List<Opportunity> opps = new List<Opportunity>();
62
+ Integer index = 0;
63
+ for (Account acc : accounts) {
64
+ for (Integer i = 0; i < countPerAccount; i++) {
65
+ opps.add(new Opportunity(
66
+ Name = 'Test Opportunity ' + index,
67
+ AccountId = acc.Id,
68
+ StageName = 'Prospecting',
69
+ CloseDate = Date.today().addDays(30),
70
+ Amount = 10000 + (index * 1000)
71
+ ));
72
+ index++;
73
+ }
74
+ }
75
+ if (doInsert) insert opps;
76
+ return opps;
77
+ }
78
+
79
+ // ============ USERS ============
80
+
81
+ public static User createUser(String profileName, Boolean doInsert) {
82
+ Profile p = [SELECT Id FROM Profile WHERE Name = :profileName LIMIT 1];
83
+ String uniqueKey = String.valueOf(DateTime.now().getTime());
84
+
85
+ User u = new User(
86
+ FirstName = 'Test',
87
+ LastName = 'User ' + uniqueKey,
88
+ Email = 'testuser' + uniqueKey + '@example.com',
89
+ Username = 'testuser' + uniqueKey + '@example.com.test',
90
+ Alias = 'tuser',
91
+ TimeZoneSidKey = 'America/Los_Angeles',
92
+ LocaleSidKey = 'en_US',
93
+ EmailEncodingKey = 'UTF-8',
94
+ LanguageLocaleKey = 'en_US',
95
+ ProfileId = p.Id
96
+ );
97
+ if (doInsert) insert u;
98
+ return u;
99
+ }
100
+
101
+ // ============ CUSTOM OBJECTS ============
102
+
103
+ // Add methods for your custom objects following the same pattern:
104
+ // public static List<MyObject__c> createMyObjects(Integer count, Boolean doInsert) { ... }
105
+ }
106
+ ```
107
+
108
+ ## Field Override Pattern
109
+
110
+ Allow callers to override default values:
111
+
112
+ ```apex
113
+ public static Account createAccount(Map<String, Object> fieldOverrides, Boolean doInsert) {
114
+ Account acc = new Account(
115
+ Name = 'Test Account',
116
+ Industry = 'Technology'
117
+ );
118
+
119
+ // Apply overrides
120
+ for (String fieldName : fieldOverrides.keySet()) {
121
+ acc.put(fieldName, fieldOverrides.get(fieldName));
122
+ }
123
+
124
+ if (doInsert) insert acc;
125
+ return acc;
126
+ }
127
+
128
+ // Usage:
129
+ Account acc = TestDataFactory.createAccount(new Map<String, Object>{
130
+ 'Name' => 'Custom Name',
131
+ 'Industry' => 'Healthcare'
132
+ }, true);
133
+ ```
134
+
135
+ ## Handling Required Fields and Validation Rules
136
+
137
+ ```apex
138
+ public static Account createAccountWithRequiredFields(Boolean doInsert) {
139
+ Account acc = new Account(
140
+ Name = 'Test Account',
141
+ // Required custom fields
142
+ External_Id__c = 'EXT-' + String.valueOf(DateTime.now().getTime()),
143
+ // Fields required by validation rules
144
+ Phone = '555-123-4567',
145
+ Website = 'https://example.com'
146
+ );
147
+ if (doInsert) insert acc;
148
+ return acc;
149
+ }
150
+ ```
151
+
152
+ ## Record Type Support
153
+
154
+ ```apex
155
+ public static Account createAccountByRecordType(String recordTypeName, Boolean doInsert) {
156
+ Id recordTypeId = Schema.SObjectType.Account
157
+ .getRecordTypeInfosByDeveloperName()
158
+ .get(recordTypeName)
159
+ .getRecordTypeId();
160
+
161
+ Account acc = new Account(
162
+ Name = 'Test Account',
163
+ RecordTypeId = recordTypeId
164
+ );
165
+ if (doInsert) insert acc;
166
+ return acc;
167
+ }
168
+ ```
169
+
170
+ ## Best Practices
171
+
172
+ 1. **Always include doInsert parameter** - Allows flexibility for tests that need to modify records before insert
173
+ 2. **Use unique identifiers** - Include index or timestamp in Name/Email fields to avoid duplicates
174
+ 3. **Set all required fields** - Include all fields required by validation rules
175
+ 4. **Return the created records** - Enables chaining and further manipulation
176
+ 5. **Create bulk methods first** - Single record methods should call bulk methods with count=1
@@ -51,6 +51,17 @@ 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
66
  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.
56
67
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generating-flexipage
3
- description: "Use this skill when users need to create, generate, modify, or validate Salesforce Lightning pages (FlexiPages). Trigger when users mention RecordPage, AppPage, HomePage, Lightning pages, page layouts, adding components to pages, or page customization. Also use when users say things like \"create a Lightning page\", \"add a component to a page\", \"customize the record page\", \"generate a FlexiPage\", or when they're working with FlexiPage XML files and need help with components, regions, or deployment errors. Always use this skill for any FlexiPage-related work, even if they just mention \"page\" in the context of Salesforce."
3
+ description: "Use this skill when users need to create, generate, modify, or validate Salesforce Lightning pages (FlexiPages). Trigger when users mention RecordPage, AppPage, HomePage, Lightning pages, page layouts, adding components to pages, or page customization. Also use when users say things like 'create a Lightning page', 'add a component to a page', 'customize the record page', 'generate a FlexiPage', or when they're working with FlexiPage XML files and need help with components, regions, or deployment errors. Always use this skill for any FlexiPage-related work, even if they just mention 'page' in the context of Salesforce."
4
4
  ---
5
5
 
6
6
  ## When to Use This Skill
@@ -20,6 +20,8 @@ Use this skill when you need to:
20
20
 
21
21
  ## Overview
22
22
 
23
+ **CRITICAL: When creating NEW FlexiPages, you MUST ALWAYS start with the CLI template command.** Never create FlexiPage XML from scratch - the CLI provides valid structure, proper regions, and correct component configuration that prevents deployment errors.
24
+
23
25
  Generate Lightning pages (RecordPage, AppPage, HomePage) using CLI bootstrapping for component discovery and configuration.
24
26
 
25
27
  ---
@@ -28,6 +30,8 @@ Generate Lightning pages (RecordPage, AppPage, HomePage) using CLI bootstrapping
28
30
 
29
31
  ### Step 1: Bootstrap with CLI
30
32
 
33
+ **MANDATORY FOR NEW PAGES: This step is NOT optional.** Always use the CLI template command when creating a new FlexiPage. The CLI generates valid XML structure, proper regions, and correct metadata that prevents common deployment errors. Only skip this step if you're editing an existing FlexiPage file.
34
+
31
35
  ```bash
32
36
  sf template generate flexipage \
33
37
  --name <PageName> \
@@ -39,19 +43,22 @@ sf template generate flexipage \
39
43
  --output-dir force-app/main/default/flexipages
40
44
  ```
41
45
 
42
- **Template-specific requirements:**
43
- - **RecordPage**: Requires `--sobject` (e.g., Account, Custom_Object__c)
44
- - **RecordPage**: Requires `--primary-field` and `--secondary-fields` for dynamic highlights, `--detail-fields` for full record details. Use the most important identifying field as primary, e.g. Name. Use the secondary fields (max 12, recommended 4-6) to show a summary of the record. Use detail fields to show the full details of the record.
45
- - **AppPage**: No additional requirements
46
- - **HomePage**: No additional requirements
47
-
48
46
  **Note:** If the `sf template generate flexipage` command fails, recommend users upgrade to the latest version of the Salesforce CLI:
49
47
  ```bash
50
48
  npm install -g @salesforce/cli@latest
51
49
  ```
52
50
 
51
+ #### **Field Selection Guidelines**
52
+ - **Validate fields exist**: Use MCP tools or describe commands to discover available fields for the object before specifying them in the command
53
+ - **Prefer compound fields**: Use `Name` (not `FirstName`/`LastName`), `BillingAddress` (not `BillingStreet`/`BillingCity`/`BillingState`), `MailingAddress`, etc. when available
53
54
 
54
- **What you get:**
55
+ #### **Template-specific requirements**
56
+ - **RecordPage**: Requires `--sobject` (e.g., Account, Custom_Object__c)
57
+ - **RecordPage**: Requires `--primary-field` and `--secondary-fields` for dynamic highlights, `--detail-fields` for full record details. Use the most important identifying field as primary, e.g. Name. Use the secondary fields (max 12, recommended 4-6) to show a summary of the record. Use detail fields to show the full details of the record.
58
+ - **AppPage**: No additional requirements
59
+ - **HomePage**: No additional requirements
60
+
61
+ #### **What you get**
55
62
  - Valid FlexiPage XML with correct structure
56
63
  - Pre-configured regions and basic components
57
64
  - Proper field references and facet structure
@@ -59,17 +66,31 @@ npm install -g @salesforce/cli@latest
59
66
 
60
67
  ### Step 2: Deploy Base Page
61
68
 
69
+ Run a **dry-run** deployment of the entire project to validate the page and dependencies:
62
70
  ```bash
63
- sf project deploy start --source-dir force-app/main/default/flexipages
71
+ sf project deploy start --dry-run -d "force-app/main/default" --test-level NoTestRun --wait 10 --json
64
72
  ```
65
73
 
66
- **Deploy early, deploy often.** Start with the bootstrapped page, validate it works, then enhance.
74
+ **Critical:** Fix any deployment errors before proceeding. The page must validate successfully.
75
+
76
+ ### Step 3: **STOP - No Further Modifications**
77
+
78
+ **MANDATORY: Stop after Step 2. Do not add components or edit the FlexiPage XML.**
67
79
 
68
- ### Step 3: Update and Redeploy
80
+ This applies even if the user requested:
81
+ - Additional components
82
+ - Page customization
83
+ - Component configuration
69
84
 
70
- Modify the generated XML, adding components discovered via MCP. Deploy incrementally.
85
+ What you CAN do:
86
+ - Suggest what components would be useful
87
+ - Explain what enhancements are possible
88
+ - Document what would need to be added manually
71
89
 
72
- **Note:** Warn users to use caution with updates beyond this step when using this command.
90
+ What you CANNOT do:
91
+ - Modify the XML file
92
+ - Add any components
93
+ - Make any enhancements
73
94
 
74
95
  ---
75
96
 
@@ -215,6 +236,10 @@ Every fieldInstance requires:
215
236
 
216
237
  ## Common Deployment Errors
217
238
 
239
+ ### "We couldn't retrieve or load the information on the field"
240
+ **Cause:** Invalid field API name - field doesn't exist on the object or has incorrect spelling
241
+ **Fix:** Use MCP tools or describe commands to discover valid fields, then update the field reference (see Field Selection Guidelines)
242
+
218
243
  ### "Invalid field reference"
219
244
  **Cause:** Used `ObjectName.Field` instead of `Record.Field`
220
245
  **Fix:** Change to `Record.{FieldApiName}`
@@ -245,49 +270,6 @@ Every fieldInstance requires:
245
270
 
246
271
  ---
247
272
 
248
- ## Incremental Development Pattern
249
-
250
- **Philosophy:** Deploy small, working increments. Don't build entire complex page at once.
251
-
252
- **Process:**
253
- 1. **CLI bootstrap** → Deploy base page
254
- 2. **Add one component** → Deploy
255
- 3. **Add another component** → Deploy
256
- 4. **Repeat** until complete
257
-
258
- **Benefits:**
259
- - Isolated errors (know exactly what broke)
260
- - Faster debugging
261
- - Build confidence with each success
262
- - Get user feedback early
263
-
264
- **Anti-pattern:** Building entire complex page → one giant error cascade.
265
-
266
- ---
267
-
268
- ## Adding Components to Existing FlexiPages
269
-
270
- ### Workflow
271
-
272
- When user provides an existing FlexiPage file path:
273
-
274
- 1. **Read the file** using native file I/O
275
- 2. **Parse XML** to extract:
276
- - **ALL existing component identifiers** (search for all `<identifier>` tags)
277
- - **ALL existing region/facet names** (search for all `<name>` tags in `<flexiPageRegions>`)
278
- - Available regions (parse from file, don't assume names)
279
- - Existing facets
280
- 3. **Verify uniqueness** - ensure your new identifiers and names don't conflict with ANY existing ones
281
- 4. **Check if target facet exists** - if adding to a named facet like `detailTabContent` that already exists:
282
- - **Add new `<itemInstances>` to existing region** (don't create duplicate region)
283
- - **Insert before the closing `</flexiPageRegions>` tag of that region**
284
- 5. **Generate component XML** (apply all rules from "Critical XML Rules" section)
285
- 6. **Insert** into appropriate region or add itemInstances to existing facet
286
- 7. **Write** modified XML back to file
287
- 8. **Deploy**: `sf project deploy start --source-dir force-app/...`
288
-
289
- ---
290
-
291
273
  ### Generating Unique Identifiers
292
274
 
293
275
  **CRITICAL: Before generating ANY new identifier or facet name, follow the rules in section 5 of "Critical XML Rules" above.**
@@ -474,7 +456,7 @@ Identifier Pattern: flexipage_richText or flexipage_richText_{sequence}
474
456
  ## Validation Checklist
475
457
 
476
458
  Before deploying:
477
- - [ ] Used CLI to bootstrap (don't start from scratch)
459
+ - [ ] **[NEW PAGES ONLY]** Used CLI to bootstrap - NEVER create FlexiPage XML from scratch
478
460
  - [ ] **ALL identifiers are unique** - no duplicate `<identifier>` values anywhere in file
479
461
  - [ ] **ALL region/facet names are unique** - no duplicate `<name>` values in `<flexiPageRegions>`
480
462
  - [ ] **Multiple components in same facet are combined** - ONE region with multiple `<itemInstances>`, NOT separate regions with same name