@salesforce/afv-skills 1.56.0 → 1.58.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 (65) hide show
  1. package/package.json +1 -1
  2. package/skills/commerce-b2b-open-code-components-integrate/SKILL.md +1 -1
  3. package/skills/commerce-b2b-open-code-components-replace/SKILL.md +16 -16
  4. package/skills/dx-devops-project-manage/SKILL.md +1 -1
  5. package/skills/experience-cms-content-generate/SKILL.md +1 -1
  6. package/skills/experience-cms-content-render/SKILL.md +3 -3
  7. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +35 -7
  8. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +13 -4
  9. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +30 -8
  10. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +14 -4
  11. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +11 -1
  12. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +1 -1
  13. package/skills/experience-cms-content-render/assets/shared/mediaLabels.ts +62 -0
  14. package/skills/experience-cms-content-render/references/init-scaffold.md +13 -6
  15. package/skills/experience-content-media-stock-image-search/SKILL.md +0 -1
  16. package/skills/experience-lwc-design-generate/SKILL.md +1 -1
  17. package/skills/experience-lwc-legacy-migrate/SKILL.md +212 -0
  18. package/skills/experience-lwc-legacy-migrate/assets/lo20-host-page-template.html +192 -0
  19. package/skills/experience-lwc-legacy-migrate/references/aura-to-lwc-completeness-checklist.md +188 -0
  20. package/skills/experience-lwc-legacy-migrate/references/lightning-out-2-system-reference.md +495 -0
  21. package/skills/experience-lwc-legacy-migrate/references/lightning-out-beta-to-2-migration.md +824 -0
  22. package/skills/experience-lwc-legacy-migrate/scripts/convert-lo-names.py +94 -0
  23. package/skills/experience-lwc-legacy-migrate/scripts/validate-lo20-page.py +133 -0
  24. package/skills/experience-ui-bundle-project-generate/SKILL.md +14 -40
  25. package/skills/experience-ui-bundle-project-generate/scripts/flatten-project.mjs +33 -11
  26. package/skills/experience-ui-bundle-project-generate/scripts/generate-project.mjs +77 -0
  27. package/skills/experience-ui-bundle-project-generate/scripts/install-deps.mjs +95 -0
  28. package/skills/integration-connectivity-connected-app-configure/SKILL.md +5 -5
  29. package/skills/integration-connectivity-generate/SKILL.md +10 -10
  30. package/skills/integration-connectivity-generate/scripts/configure-named-credential.sh +2 -2
  31. package/skills/platform-apex-test-generate/SKILL.md +1 -1
  32. package/skills/platform-apex-test-run/SKILL.md +1 -1
  33. package/skills/platform-custom-field-generate/SKILL.md +1 -1
  34. package/skills/platform-custom-lightning-type-generate/SKILL.md +1 -1
  35. package/skills/platform-custom-metadata-type-generate/SKILL.md +1 -1
  36. package/skills/platform-custom-object-generate/SKILL.md +1 -1
  37. package/skills/platform-custom-report-type-generate/SKILL.md +1 -1
  38. package/skills/platform-custom-setting-generate/SKILL.md +1 -1
  39. package/skills/platform-data-and-tooling-api-context-get/SKILL.md +1 -1
  40. package/skills/platform-data-manage/SKILL.md +10 -10
  41. package/skills/platform-flexipage-generate/SKILL.md +1 -1
  42. package/skills/platform-lightning-app-coordinate/SKILL.md +1 -1
  43. package/skills/platform-metadata-api-context-get/SKILL.md +1 -1
  44. package/skills/platform-metadata-retrieve/SKILL.md +1 -1
  45. package/skills/platform-permission-set-generate/SKILL.md +1 -1
  46. package/skills/platform-report-generate/SKILL.md +1 -1
  47. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +4 -4
  48. package/skills/platform-sharing-owd-configure/SKILL.md +1 -1
  49. package/skills/platform-sharing-rules-generate/SKILL.md +1 -1
  50. package/skills/platform-soql-query/SKILL.md +1 -1
  51. package/skills/platform-value-set-generate/SKILL.md +1 -1
  52. package/skills/service-agentforce-human-escalation-configure/SKILL.md +4 -0
  53. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +26 -6
  54. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +5 -4
  55. package/skills/service-de-waba-integrate/SKILL.md +2 -1
  56. package/skills/service-digital-engagement-deployment-configure/SKILL.md +2 -0
  57. package/skills/service-digital-engagement-deployment-configure/scripts/check-api-version.sh +29 -0
  58. package/skills/service-email-to-case-configure/SKILL.md +1 -1
  59. package/skills/service-omni-channel-setup-coordinate/SKILL.md +1 -0
  60. package/skills/service-omni-command-center-analyze/SKILL.md +1 -0
  61. package/skills/service-omni-command-center-configure/SKILL.md +92 -0
  62. package/skills/service-omni-command-center-configure/references/api-notes.md +50 -0
  63. package/skills/service-omni-command-center-configure/scripts/configure-and-report.sh +257 -0
  64. package/skills/service-omni-command-center-configure/scripts/settings_document.py +97 -0
  65. package/skills/service-omni-command-center-configure/scripts/tests/test_command_center_configure_contracts.py +206 -0
@@ -0,0 +1,824 @@
1
+ # Lightning Out Beta → Lightning Out 2.0 Migration Guide
2
+
3
+ ## Migration Approach
4
+
5
+ This guide helps you identify and transform Lightning Out Beta code to Lightning Out 2.0. **Your existing HTML structure, styling, and custom logic remain unchanged** - we only modify Lightning Out-specific patterns.
6
+
7
+ ---
8
+
9
+ ## Lightning Out 2.0 Documentation
10
+
11
+ Before applying the migration steps below, internalize the complete Lightning Out 2.0 architecture, lifecycle, styling, events, and constraints documentation in the companion reference:
12
+
13
+ See [lightning-out-2-system-reference.md](lightning-out-2-system-reference.md) for the complete LO 2.0 system reference.
14
+
15
+ ---
16
+
17
+ ## Phase 1: Identify Lightning Out Beta Patterns
18
+
19
+ **Search for these patterns in the customer's code:**
20
+
21
+ ### Pattern A: Script Tag Loading Beta Library
22
+
23
+ ```html
24
+ <script src="https://[DOMAIN].lightning.force.com/lightning/lightning.out.js"></script>
25
+ ```
26
+
27
+ **What to extract:**
28
+
29
+ - Domain (before `.lightning.force.com`)
30
+
31
+ ### Pattern B: Configuration Variables
32
+
33
+ ```javascript
34
+ const [ENDPOINT_VAR] = 'https://[DOMAIN].lightning.force.com';
35
+ const [APP_VAR] = 'c:[AuraAppName]'; // Aura app name
36
+ const [COMPONENT_VAR] = 'c:[componentName]'; // Component name
37
+ const [TOKEN_VAR] = '[AUTH_TOKEN]'; // Hardcoded token
38
+ ```
39
+
40
+ **What to extract:**
41
+
42
+ - Endpoint variable name and domain value
43
+ - Aura app variable name and value
44
+ - Component variable name and value (Aura format: `c:name`)
45
+ - Token variable name
46
+
47
+ ### Pattern C: $Lightning.use() Call
48
+
49
+ ```javascript
50
+ $Lightning.use(
51
+ [APP_VAR], // 1st param: Aura app (could be variable or string)
52
+ function () {
53
+ // Callback function body
54
+ },
55
+ [ENDPOINT_VAR], // 3rd param: Endpoint (could be variable or string)
56
+ [TOKEN_VAR], // 4th param: Auth token (could be variable or string)
57
+ );
58
+ ```
59
+
60
+ **What to extract:**
61
+
62
+ - The entire `$Lightning.use()` block
63
+ - First parameter value (Aura app)
64
+ - Third parameter value (endpoint)
65
+ - Fourth parameter value (token)
66
+
67
+ ### Pattern D: $Lightning.createComponent() Call
68
+
69
+ ```javascript
70
+ $Lightning.createComponent(
71
+ [COMPONENT_VAR], // 1st param: Component name (could be variable or string "c:name")
72
+ {[ATTRIBUTES]}, // 2nd param: Attributes object (could be empty {})
73
+ "[DOM_ID]", // 3rd param: DOM element ID as STRING
74
+ function(cmp) {
75
+ // Optional callback
76
+ [CUSTOM_LOGIC]
77
+ }
78
+ );
79
+ ```
80
+
81
+ **What to extract:**
82
+
83
+ - Component name/variable
84
+ - Attributes object (if any): `{ key: "value", anotherKey: true }`
85
+ - DOM element ID (3rd parameter - this is where component renders)
86
+ - Any custom logic inside the callback
87
+
88
+ ### Pattern E: DOM Target Element
89
+
90
+ ```html
91
+ <[ANY_TAG] id="[DOM_ID]">[CONTENT]</[ANY_TAG]>
92
+ ```
93
+
94
+ Where `[DOM_ID]` matches the 3rd parameter in `$Lightning.createComponent()`
95
+
96
+ **What to extract:**
97
+
98
+ - Element location in HTML
99
+ - Element tag name and existing attributes
100
+
101
+ ---
102
+
103
+ ## Phase 2: Apply Transformations
104
+
105
+ **Note**: Examples below use generic component names like "c-lo-beta-entry-form". Replace ALL occurrences with your actual component name from Phase 1.
106
+
107
+ ### Transform 1: Update Script Tag
108
+
109
+ **Find Pattern A:**
110
+
111
+ ```html
112
+ <script src="https://[DOMAIN].lightning.force.com/lightning/lightning.out.js"></script>
113
+ ```
114
+
115
+ **Replace with:**
116
+
117
+ ```html
118
+ <script async src="https://[DOMAIN].my.salesforce.com/lightning/lightning.out.latest/index.iife.prod.js"></script>
119
+ ```
120
+
121
+ **Rules:**
122
+
123
+ - Change domain: `.lightning.force.com` → `.my.salesforce.com`
124
+ - Change path: `/lightning/lightning.out.js` → `/lightning/lightning.out.latest/index.iife.prod.js`
125
+ - Add `async` attribute
126
+
127
+ ### Transform 2: Convert Component Name
128
+
129
+ **From Pattern B or D, extract the component name:**
130
+
131
+ - If variable: `const component = "c:myComponent"` → extract `"c:myComponent"`
132
+ - If inline: `$Lightning.createComponent("c:myComponent", ...)` → extract `"c:myComponent"`
133
+
134
+ **Convert Aura format to kebab-case:**
135
+
136
+ - `c:myComponent` → `c-my-component`
137
+ - `c:loBetaEntryForm` → `c-lo-beta-entry-form`
138
+ - `c:userDashboard` → `c-user-dashboard`
139
+
140
+ **Conversion algorithm:**
141
+
142
+ 1. Remove `c:` prefix
143
+ 2. Insert hyphen before each capital letter
144
+ 3. Convert to lowercase
145
+ 4. Add `c-` prefix
146
+
147
+ ### Transform 3: Add Component Tag to HTML
148
+
149
+ **At the location of Pattern E** (the DOM target element), add:
150
+
151
+ ```html
152
+ <!-- Add these BEFORE or INSIDE the existing target element -->
153
+ <div id="[MOUNT_ID]"></div> <!-- Choose a mount point ID -->
154
+ <[KEBAB-COMPONENT-TAG] [ATTRIBUTES]></[KEBAB-COMPONENT-TAG]>
155
+ ```
156
+
157
+ **Where:**
158
+
159
+ - `[MOUNT_ID]` = Your chosen ID for the mount point (e.g., "lo2Mount", "sfAppMount")
160
+ - `[KEBAB-COMPONENT-TAG]` = converted component name from Transform 2
161
+ - `[ATTRIBUTES]` = converted attributes from Pattern D (see below)
162
+
163
+ **If Pattern D had attributes object:**
164
+
165
+ ```javascript
166
+ { recordId: "123", isActive: true, userName: "John" }
167
+ ```
168
+
169
+ **Convert to HTML attributes (camelCase → kebab-case):**
170
+
171
+ ```html
172
+ <c-my-component record-id="123" is-active="true" user-name="John"></c-my-component>
173
+ ```
174
+
175
+ ### Transform 4: Replace Beta JavaScript Block
176
+
177
+ **Delete entire Patterns C and D block:**
178
+
179
+ ```javascript
180
+ // DELETE THIS ENTIRE SECTION
181
+ const [ENDPOINT_VAR] = "...";
182
+ const [APP_VAR] = "c:...";
183
+ const [COMPONENT_VAR] = "c:...";
184
+ const [TOKEN_VAR] = "...";
185
+
186
+ $Lightning.use([APP_VAR], function() {
187
+ $Lightning.createComponent([COMPONENT_VAR], {...}, "[DOM_ID]", function(cmp) {
188
+ [CUSTOM_LOGIC]
189
+ });
190
+ }, [ENDPOINT_VAR], [TOKEN_VAR]);
191
+ ```
192
+
193
+ **Replace with LO 2.0 pattern:**
194
+
195
+ ```javascript
196
+ // Configuration (NEW - adapt names to customer's style)
197
+ const ECA_CONSUMER_KEY = '[GET_FROM_SALESFORCE_SETUP]';
198
+ const LO_APP_ID = '[GET_FROM_SALESFORCE_SETUP]'; // Optional 18-character ID (omit if not required)
199
+ const ORG_MYDOMAIN_URL = 'https://[DOMAIN].my.salesforce.com'; // Transform from extracted domain
200
+ const components = '[KEBAB-COMPONENT-NAME]'; // From Transform 2
201
+
202
+ // Helper functions (REQUIRED - copy exactly)
203
+ function readCachedResult() {
204
+ try {
205
+ const raw = localStorage.getItem('lo2_frontdoor_result');
206
+ if (!raw) return null;
207
+ return JSON.parse(raw);
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+
213
+ function clearCachedResult() {
214
+ try {
215
+ localStorage.removeItem('lo2_frontdoor_result');
216
+ } catch {}
217
+ }
218
+
219
+ // Mount function (adapt element selection to customer's DOM)
220
+ function mountLo20(frontdoorUrl, orgUrl) {
221
+ const [MOUNT_ELEMENT] = document.getElementById('[MOUNT_ID]'); // Customer's mount point
222
+ if ([MOUNT_ELEMENT] && [MOUNT_ELEMENT].querySelector('lightning-out-application')) return;
223
+
224
+ function tryMount() {
225
+ if (!customElements?.get?.('lightning-out-application')) return false;
226
+
227
+ const el = document.createElement('lightning-out-application');
228
+ el.setAttribute('components', components);
229
+ if (LO_APP_ID) el.setAttribute('app-id', LO_APP_ID);
230
+ el.setAttribute('frontdoor-url', frontdoorUrl);
231
+
232
+ ([MOUNT_ELEMENT] || document.body).appendChild(el);
233
+ return true;
234
+ }
235
+
236
+ const start = Date.now();
237
+ (function tick() {
238
+ if (tryMount()) return;
239
+ if (Date.now() - start > 15000) {
240
+ console.error('LO 2.0 mount timeout');
241
+ // Optionally show error in customer's error display
242
+ return;
243
+ }
244
+ setTimeout(tick, 50);
245
+ })();
246
+
247
+ // CRITICAL: Use literal component name string (NOT variable)
248
+ customElements.whenDefined('[LITERAL-KEBAB-COMPONENT-NAME]').then(() => {
249
+ // Hide customer's loading indicator here
250
+ // Example: document.getElementById('[CUSTOMER_LOADING_ID]').style.display = 'none';
251
+
252
+ // If Pattern D had custom callback logic, add it here:
253
+ [CUSTOM_LOGIC_FROM_CALLBACK];
254
+ });
255
+ }
256
+
257
+ // Boot function (wrap all initialization)
258
+ function boot() {
259
+ try {
260
+ // Store OAuth config for callback page
261
+ localStorage.setItem('orgMyDomainURL', ORG_MYDOMAIN_URL);
262
+ localStorage.setItem('loAppID', LO_APP_ID);
263
+ localStorage.setItem('loECAKey', ECA_CONSUMER_KEY);
264
+ localStorage.setItem('lo2_returnTo', window.location.pathname);
265
+
266
+ // OAuth callback listeners
267
+ window.addEventListener('message', (evt) => {
268
+ const data = evt?.data;
269
+ if (!data || data.error || !data.frontdoorUrl) return;
270
+ clearCachedResult();
271
+ mountLo20(data.frontdoorUrl, data.orgUrl);
272
+ });
273
+
274
+ try {
275
+ const bc = new BroadcastChannel('lo2auth');
276
+ bc.onmessage = (evt) => {
277
+ const data = evt?.data;
278
+ if (!data || data.error || !data.frontdoorUrl) return;
279
+ clearCachedResult();
280
+ mountLo20(data.frontdoorUrl, data.orgUrl);
281
+ };
282
+ } catch (e) {}
283
+
284
+ window.addEventListener('storage', (evt) => {
285
+ if (evt.key !== 'lo2_frontdoor_result') return;
286
+ const data = readCachedResult();
287
+ if (!data || data.error || !data.frontdoorUrl) return;
288
+ mountLo20(data.frontdoorUrl, data.orgUrl);
289
+ });
290
+
291
+ // Check for cached auth
292
+ const cached = readCachedResult();
293
+ if (cached?.frontdoorUrl) {
294
+ mountLo20(cached.frontdoorUrl, cached.orgUrl);
295
+ }
296
+
297
+ // Wire up customer's OAuth trigger (button/link/etc)
298
+ const [CUSTOMER_TRIGGER_ELEMENT] = document.getElementById('[CUSTOMER_TRIGGER_ID]');
299
+ if ([CUSTOMER_TRIGGER_ELEMENT]) {
300
+ [CUSTOMER_TRIGGER_ELEMENT].addEventListener('click', () => {
301
+ clearCachedResult();
302
+ window.location.assign('/frontdoor-url.html');
303
+ });
304
+ }
305
+ } catch (e) {
306
+ console.error('Boot error:', e);
307
+ }
308
+ }
309
+
310
+ boot();
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Phase 3: Integration with Customer Code
316
+
317
+ ### Preserve Customer's:
318
+
319
+ 1. **HTML structure** - Don't modify layout, styling, or element hierarchy
320
+ 2. **Loading indicators** - Hook into existing UI, don't create new ones
321
+ 3. **Error handling** - Use existing error display mechanisms
322
+ 4. **Event handlers** - Preserve any custom event listeners
323
+ 5. **Business logic** - Move callback logic from Pattern D to `customElements.whenDefined().then()`
324
+
325
+ ### Adapt These Elements:
326
+
327
+ 1. **Element selectors** - Use customer's actual element IDs (don't hardcode "loAppMount", "connectBtn")
328
+ 2. **Loading indicator** - In `customElements.whenDefined()`, hide customer's loading element
329
+ 3. **OAuth trigger** - Wire up customer's existing button/link for authentication
330
+ 4. **Error display** - If customer has error handling, integrate with that
331
+
332
+ ### Example Integration:
333
+
334
+ **Customer's Beta code had:**
335
+
336
+ ```html
337
+ <div id="myContainer">
338
+ <div class="spinner">Loading...</div>
339
+ <div id="lightningTarget"></div>
340
+ </div>
341
+ <button onclick="doAuth()">Login</button>
342
+ ```
343
+
344
+ **After migration:**
345
+
346
+ ```html
347
+ <div id="myContainer">
348
+ <div class="spinner">Loading...</div>
349
+ <div id="sfMount"></div>
350
+ <!-- ADD: Choose your own ID -->
351
+ <c-my-component></c-my-component>
352
+ <!-- ADD -->
353
+ <div id="lightningTarget"></div>
354
+ <!-- KEEP if needed -->
355
+ </div>
356
+ <button id="authBtn">Login</button>
357
+ <!-- ADD id for event listener -->
358
+ ```
359
+
360
+ **In JavaScript, use customer's actual IDs:**
361
+
362
+ ```javascript
363
+ const sfMount = document.getElementById('sfMount'); // Use YOUR mount ID
364
+
365
+ function mountLo20(frontdoorUrl, orgUrl) {
366
+ if (sfMount && sfMount.querySelector('lightning-out-application')) return;
367
+ // ... rest of mount logic using sfMount
368
+ (sfMount || document.body).appendChild(el);
369
+ }
370
+
371
+ // In boot(), hide customer's spinner when ready
372
+ customElements.whenDefined('c-my-component').then(() => {
373
+ document.querySelector('.spinner').style.display = 'none'; // Customer's spinner
374
+ });
375
+
376
+ // Wire up customer's button
377
+ document.getElementById('authBtn').addEventListener('click', () => {
378
+ clearCachedResult();
379
+ window.location.assign('/frontdoor-url.html');
380
+ });
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Critical Implementation Rules
386
+
387
+ **These rules prevent runtime errors - follow exactly:**
388
+
389
+ ### Rule 1: `customElements.whenDefined()` requires literal string
390
+
391
+ WRONG — Will fail:
392
+
393
+ ```javascript
394
+ const components = "c-my-component";
395
+ customElements.whenDefined(components).then(...); // Using variable
396
+ ```
397
+
398
+ CORRECT:
399
+
400
+ ```javascript
401
+ const components = "c-my-component";
402
+ customElements.whenDefined('c-my-component').then(...); // Literal string
403
+ ```
404
+
405
+ ### Rule 2: `mountLo20()` must accept two parameters
406
+
407
+ CORRECT signature (even if orgUrl unused):
408
+
409
+ ```javascript
410
+ function mountLo20(frontdoorUrl, orgUrl) {
411
+ // Implementation
412
+ }
413
+ ```
414
+
415
+ ### Rule 3: Always call `clearCachedResult()` before mounting
416
+
417
+ In every OAuth listener:
418
+
419
+ ```javascript
420
+ if (data?.frontdoorUrl) {
421
+ clearCachedResult(); // Must call first
422
+ mountLo20(data.frontdoorUrl, data.orgUrl);
423
+ }
424
+ ```
425
+
426
+ ### Rule 4: Pass both parameters when calling mountLo20
427
+
428
+ Always:
429
+
430
+ ```javascript
431
+ mountLo20(data.frontdoorUrl, data.orgUrl); // Both parameters, even if orgUrl is undefined
432
+ ```
433
+
434
+ ### Rule 5: Wrap all initialization in `boot()` function
435
+
436
+ Required structure:
437
+
438
+ ```javascript
439
+ function boot() {
440
+ try {
441
+ // All localStorage setup
442
+ // All event listeners
443
+ // All initialization
444
+ } catch (e) {
445
+ console.error('Boot error:', e);
446
+ }
447
+ }
448
+ boot(); // Must call at end
449
+ ```
450
+
451
+ ### Rule 6: Component tag has no inline styles
452
+
453
+ CORRECT:
454
+
455
+ ```html
456
+ <c-my-component></c-my-component>
457
+ ```
458
+
459
+ WRONG:
460
+
461
+ ```html
462
+ <c-my-component style="display: none"></c-my-component>
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Transformation Patterns
468
+
469
+ ### 1. Domain Transformation
470
+
471
+ **Pattern**: `https://[subdomain].lightning.[pod].force.com` → `https://[subdomain].my.[pod].salesforce.com`
472
+
473
+ ```javascript
474
+ // BEFORE (Beta)
475
+ const endpoint = 'https://orgfarm-5b43fe90c6.test1.lightning.pc-rnd.force.com';
476
+
477
+ // AFTER (LO 2.0)
478
+ const ORG_MYDOMAIN_URL = 'https://orgfarm-5b43fe90c6.test1.my.pc-rnd.salesforce.com';
479
+ ```
480
+
481
+ ### 2. Library Script Tag
482
+
483
+ ```html
484
+ <!-- BEFORE (Beta) -->
485
+ <script src="https://YOUR_ORG.lightning.force.com/lightning/lightning.out.js"></script>
486
+
487
+ <!-- AFTER (LO 2.0) -->
488
+ <script async src="https://YOUR_ORG.my.salesforce.com/lightning/lightning.out.latest/index.iife.prod.js"></script>
489
+ ```
490
+
491
+ ### 3. Component Name Conversion
492
+
493
+ ```javascript
494
+ // BEFORE (Beta): Aura-style naming
495
+ const component = 'c:loBetaEntryForm';
496
+
497
+ // AFTER (LO 2.0): Kebab-case
498
+ const components = 'c-lo-beta-entry-form';
499
+ ```
500
+
501
+ ### 4. Component Initialization - Complete Replacement
502
+
503
+ **REMOVE this entire Beta pattern:**
504
+
505
+ ```javascript
506
+ $Lightning.use(
507
+ 'c:OrgFarmOut',
508
+ function () {
509
+ $Lightning.createComponent(
510
+ 'c:loBetaEntryForm',
511
+ { contactId: '003...' }, // attributes
512
+ 'lightning-out', // DOM target
513
+ function (cmp) {
514
+ console.log('created');
515
+ },
516
+ );
517
+ },
518
+ 'https://org.lightning.force.com',
519
+ '00D...TOKEN...', // hardcoded token
520
+ );
521
+ ```
522
+
523
+ **REPLACE with LO 2.0 pattern:**
524
+
525
+ **Step A**: Add component tag directly in HTML:
526
+
527
+ ```html
528
+ <div id="loAppMount"></div>
529
+ <c-lo-beta-entry-form contact-id="003..."></c-lo-beta-entry-form>
530
+ ```
531
+
532
+ _Note: Attributes converted from camelCase to kebab-case_
533
+
534
+ **Step B**: Create mounting function:
535
+
536
+ ```javascript
537
+ function mountLo20(frontdoorUrl) {
538
+ // Poll for custom element availability
539
+ function tryMount() {
540
+ if (!customElements?.get?.('lightning-out-application')) return false;
541
+
542
+ const el = document.createElement('lightning-out-application');
543
+ el.setAttribute('components', 'c-lo-beta-entry-form');
544
+ // app-id is optional — set it only if your LO 2.0 app requires it
545
+ // el.setAttribute('app-id', 'YOUR_18_CHAR_APP_ID');
546
+ el.setAttribute('frontdoor-url', frontdoorUrl);
547
+
548
+ document.getElementById('loAppMount').appendChild(el);
549
+ return true;
550
+ }
551
+
552
+ // Retry with timeout
553
+ const start = Date.now();
554
+ (function tick() {
555
+ if (tryMount()) return;
556
+ if (Date.now() - start > 15000) {
557
+ console.error('Timeout: LO 2.0 not registered');
558
+ return;
559
+ }
560
+ setTimeout(tick, 50);
561
+ })();
562
+ }
563
+ ```
564
+
565
+ ### 5. Authentication Replacement
566
+
567
+ **BEFORE (Beta)**: Hardcoded token
568
+
569
+ ```javascript
570
+ const authToken = '<LEGACY_SESSION_TOKEN_PLACEHOLDER>'; // Remove this
571
+ ```
572
+
573
+ **AFTER (LO 2.0)**: OAuth PKCE flow
574
+
575
+ **Step A**: Store OAuth config in localStorage:
576
+
577
+ ```javascript
578
+ localStorage.setItem('orgMyDomainURL', 'https://YOUR_ORG.my.salesforce.com');
579
+ localStorage.setItem('loAppID', 'YOUR_18_CHAR_APP_ID');
580
+ localStorage.setItem('loECAKey', 'YOUR_ECA_CONSUMER_KEY');
581
+ localStorage.setItem('lo2_returnTo', '/your-page.html');
582
+ ```
583
+
584
+ **Step B**: Trigger OAuth flow (redirect to callback page):
585
+
586
+ ```javascript
587
+ // User clicks "Connect"
588
+ window.location.assign('/frontdoor-url.html');
589
+ ```
590
+
591
+ **Step C**: Listen for OAuth result via multiple channels:
592
+
593
+ ```javascript
594
+ // postMessage
595
+ window.addEventListener('message', (evt) => {
596
+ if (evt.data?.frontdoorUrl) {
597
+ mountLo20(evt.data.frontdoorUrl);
598
+ }
599
+ });
600
+
601
+ // BroadcastChannel
602
+ const bc = new BroadcastChannel('lo2auth');
603
+ bc.onmessage = (evt) => {
604
+ if (evt.data?.frontdoorUrl) {
605
+ mountLo20(evt.data.frontdoorUrl);
606
+ }
607
+ };
608
+
609
+ // localStorage (cross-tab)
610
+ window.addEventListener('storage', (evt) => {
611
+ if (evt.key === 'lo2_frontdoor_result') {
612
+ const data = JSON.parse(localStorage.getItem('lo2_frontdoor_result'));
613
+ if (data?.frontdoorUrl) mountLo20(data.frontdoorUrl);
614
+ }
615
+ });
616
+ ```
617
+
618
+ ### 6. Component Attribute Mapping
619
+
620
+ ```javascript
621
+ // BEFORE (Beta): JS object
622
+ $Lightning.createComponent("c:myComp", {
623
+ contactId: "003...",
624
+ recordId: "001...",
625
+ isActive: true,
626
+ count: 5
627
+ }, ...);
628
+
629
+ // AFTER (LO 2.0): HTML attributes (kebab-case)
630
+ ```
631
+
632
+ ```html
633
+ <c-my-comp contact-id="003..." record-id="001..." is-active="true" count="5"> </c-my-comp>
634
+ ```
635
+
636
+ ### 7. Component Readiness Detection
637
+
638
+ ```javascript
639
+ // BEFORE (Beta): Callback
640
+ $Lightning.createComponent(..., function(cmp) {
641
+ console.log("Component ready");
642
+ });
643
+
644
+ // AFTER (LO 2.0): Web Component lifecycle
645
+ customElements.whenDefined("c-my-component").then(() => {
646
+ console.log("Component ready");
647
+ document.getElementById("loading").style.display = "none";
648
+ });
649
+ ```
650
+
651
+ ### 8. Salesforce App Mapping
652
+
653
+ ```javascript
654
+ // BEFORE (Beta): Aura app name
655
+ const auraApp = 'c:OrgFarmOut'; // No longer used
656
+
657
+ // AFTER (LO 2.0): Lightning Out 2.0 app ID (optional, 18 chars)
658
+ const LO_APP_ID = '<LIGHTNING_OUT_APP_ID_PLACEHOLDER>'; // From Setup → Lightning Out Apps (omit if not required)
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Required Setup in Salesforce
664
+
665
+ ### 1. Create Lightning Out 2.0 App (Optional)
666
+
667
+ - Setup → Lightning Out → New Lightning Out App
668
+ - Copy the **18-character App ID** (only needed if your deployment requires an explicit app ID)
669
+
670
+ ### 2. Create External Client App (ECA)
671
+
672
+ - Setup → App Manager → New Connected App → External Client App
673
+ - Enable OAuth Settings with PKCE
674
+ - Copy the **Consumer Key**
675
+ - Add your callback URL to allowed domains
676
+
677
+ ### 3. Helper Files (Assumption)
678
+
679
+ This guide assumes you have:
680
+
681
+ - `frontdoor-url.html` — OAuth callback page
682
+ - `utils/LightningOutAuth.js` — OAuth PKCE implementation
683
+
684
+ ---
685
+
686
+ ## Essential Code Blocks
687
+
688
+ The complete, copyable host-page implementation lives in
689
+ [`assets/lo20-host-page-template.html`](../assets/lo20-host-page-template.html).
690
+ Copy that file as your starting point and fill in the `CONFIGURATION`
691
+ constants, then replace `c-your-component` with the migrated component tag and
692
+ its kebab-case attributes (use `scripts/convert-lo-names.py` to derive them).
693
+ The template is a single self-consistent host page that already assembles every
694
+ required piece:
695
+
696
+ - **Configuration** constants (ECA consumer key, optional LO 2.0 App id, My Domain URL, `components`).
697
+ - **DOM references** declared once at the top.
698
+ - The four **helper functions**: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult`.
699
+ - The two-parameter **`mountLo20(frontdoorUrl, orgUrl)`** function with a duplicate-mount guard and a literal-string `customElements.whenDefined(...)` gate.
700
+ - The **`boot()`** wrapper holding all initialization — localStorage config, the postMessage / BroadcastChannel / storage listeners, the cached-result check, and the connect-button handler — with `boot()` called once at the end.
701
+
702
+ **Do NOT** hand-reassemble these blocks into separate `initializeOAuth()` /
703
+ `setupCallbackListeners()` functions, run init code at script level outside
704
+ `boot()`, or omit the trailing `boot()` call. After filling in your copy, run
705
+ `python3 scripts/validate-lo20-page.py <file>` to confirm the six
706
+ non-negotiable rules still hold.
707
+
708
+ ---
709
+
710
+ ## Common Pitfalls
711
+
712
+ | Issue | Problem | Solution |
713
+ | ------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
714
+ | **Missing boot() wrapper** | Init code runs at script level | Wrap ALL init logic in `function boot() { ... }` and call `boot()` at end |
715
+ | **Component display logic** | Adding `display:none` to component tag | Control visibility via `loadingEl.style.display`, NOT on component tag |
716
+ | **Missing helper functions** | Undefined `setLoading()`, `showError()`, etc. | Include all 4 helper functions: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult` |
717
+ | **Wrong customElements.whenDefined** | Using variable: `customElements.whenDefined(components)` | Use literal string: `customElements.whenDefined('c-my-component')` |
718
+ | **Missing clearCachedResult()** | Not clearing cache before mount | Call `clearCachedResult()` before every `mountLo20()` call |
719
+ | **BroadcastChannel not wrapped** | BroadcastChannel creation fails in some browsers | Wrap in try-catch: `try { const bc = new BroadcastChannel(...) } catch (e) {}` |
720
+ | **Domain mismatch** | Using `.lightning.force.com` | Change to `.my.salesforce.com` |
721
+ | **Component not defined** | Wrong kebab-case format | `c:myComponent` → `c-my-component` |
722
+ | **Missing attributes** | Using camelCase in HTML | `contactId` → `contact-id` |
723
+ | **No component tag** | Only creating `<lightning-out-application>` | Add `<c-component>` tag to HTML |
724
+
725
+ Other pitfalls to watch for: missing defensive checks (`!data` / `data.error` /
726
+ `!data.frontdoorUrl`) in a listener; inline `document.getElementById()` calls
727
+ instead of DOM-reference constants; mount timeouts (check the `app-id` and
728
+ library URL); calling `mountLo20()` more than once without a duplicate-mount
729
+ guard; and OAuth not starting because the localStorage config was not set
730
+ before the redirect. `scripts/validate-lo20-page.py` catches the structural
731
+ ones automatically.
732
+
733
+ ---
734
+
735
+ ## Verification Checklist
736
+
737
+ After migration, verify:
738
+
739
+ **Script Structure:**
740
+
741
+ - [ ] All init logic wrapped in `function boot() { ... }`
742
+ - [ ] `boot()` called at the end of script
743
+ - [ ] DOM references declared as constants at top
744
+ - [ ] All 4 helper functions present: `setLoading`, `showError`, `readCachedResult`, `clearCachedResult`
745
+
746
+ **Mount Function:**
747
+
748
+ - [ ] `mountLo20(frontdoorUrl, orgUrl)` takes TWO parameters
749
+ - [ ] Duplicate mount check present
750
+ - [ ] `customElements.whenDefined()` uses literal string, not variable
751
+ - [ ] `(loAppMount || document.body).appendChild(el)` fallback present
752
+
753
+ **OAuth & Listeners:**
754
+
755
+ - [ ] `clearCachedResult()` called before every `mountLo20()` call
756
+ - [ ] All three listeners present: postMessage, BroadcastChannel, storage
757
+ - [ ] BroadcastChannel wrapped in try-catch
758
+ - [ ] Each listener checks: `!data`, `data.error`, `!data.frontdoorUrl`
759
+ - [ ] Cached result check present before any user interaction
760
+
761
+ **HTML:**
762
+
763
+ - [ ] Component tag has NO inline `display:none` style
764
+ - [ ] Both `<div id="loAppMount"></div>` and `<c-component>` tag present
765
+ - [ ] Loading indicator uses proper structure with nested divs
766
+
767
+ **Configuration:**
768
+
769
+ - [ ] Domain changed to `.my.salesforce.com`
770
+ - [ ] Library path uses `/lightning.out.latest/index.iife.prod.js`
771
+ - [ ] Component name is kebab-case
772
+ - [ ] LO 2.0 app ID is 18 characters (if used — app-id is optional)
773
+ - [ ] ECA consumer key is correct
774
+ - [ ] Component attributes are kebab-case
775
+ - [ ] `lo2_returnTo` matches current filename
776
+
777
+ ---
778
+
779
+ ## Quick Migration Workflow
780
+
781
+ 1. **Extract from Beta page:**
782
+
783
+ - Domain (endpoint URL)
784
+ - Component name (Aura format)
785
+ - Attributes object
786
+ - Aura app name
787
+
788
+ 2. **Transform:**
789
+
790
+ - Domain: `.lightning.force.com` → `.my.salesforce.com`
791
+ - Component: `c:name` → `c-name`
792
+ - Attributes: `{camelCase: val}` → `kebab-case="val"`
793
+
794
+ 3. **Setup in Salesforce:**
795
+
796
+ - Create LO 2.0 App → get 18-char ID (optional)
797
+ - Create ECA → get consumer key
798
+
799
+ 4. **Update HTML:**
800
+
801
+ - Change script src
802
+ - Add mount point div
803
+ - Add component tag with attributes
804
+ - Remove `$Lightning` code
805
+
806
+ 5. **Add JavaScript:**
807
+
808
+ - Config constants
809
+ - mountLo20() function
810
+ - localStorage setup
811
+ - OAuth listeners
812
+ - Connect button handler
813
+
814
+ 6. **Verify layout:**
815
+
816
+ - Component fills the same space as the Beta version (no clipping or scrolling)
817
+ - No fixed height on mount point or parent containers
818
+ - All form fields visible without scrolling within the component area
819
+
820
+ 7. **Test:**
821
+ - Click "Connect to Salesforce"
822
+ - Complete OAuth
823
+ - Verify component renders with full content visible
824
+ - Check browser console for errors