scc-universal 1.1.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 (271) hide show
  1. package/.claude-plugin/plugin.json +44 -0
  2. package/.cursor/agents/deep-researcher.md +142 -0
  3. package/.cursor/agents/doc-updater.md +219 -0
  4. package/.cursor/agents/eval-runner.md +335 -0
  5. package/.cursor/agents/learning-engine.md +210 -0
  6. package/.cursor/agents/loop-operator.md +245 -0
  7. package/.cursor/agents/refactor-cleaner.md +119 -0
  8. package/.cursor/agents/sf-admin-agent.md +127 -0
  9. package/.cursor/agents/sf-agentforce-agent.md +126 -0
  10. package/.cursor/agents/sf-apex-agent.md +117 -0
  11. package/.cursor/agents/sf-architect.md +426 -0
  12. package/.cursor/agents/sf-aura-reviewer.md +369 -0
  13. package/.cursor/agents/sf-bugfix-agent.md +101 -0
  14. package/.cursor/agents/sf-flow-agent.md +155 -0
  15. package/.cursor/agents/sf-integration-agent.md +141 -0
  16. package/.cursor/agents/sf-lwc-agent.md +123 -0
  17. package/.cursor/agents/sf-review-agent.md +357 -0
  18. package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
  19. package/.cursor/hooks/adapter.js +81 -0
  20. package/.cursor/hooks/after-file-edit.js +26 -0
  21. package/.cursor/hooks/after-mcp-execution.js +12 -0
  22. package/.cursor/hooks/after-shell-execution.js +30 -0
  23. package/.cursor/hooks/after-tab-file-edit.js +12 -0
  24. package/.cursor/hooks/before-mcp-execution.js +11 -0
  25. package/.cursor/hooks/before-read-file.js +13 -0
  26. package/.cursor/hooks/before-shell-execution.js +29 -0
  27. package/.cursor/hooks/before-submit-prompt.js +23 -0
  28. package/.cursor/hooks/pre-compact.js +7 -0
  29. package/.cursor/hooks/session-end.js +10 -0
  30. package/.cursor/hooks/session-start.js +10 -0
  31. package/.cursor/hooks/stop.js +18 -0
  32. package/.cursor/hooks/subagent-start.js +10 -0
  33. package/.cursor/hooks/subagent-stop.js +10 -0
  34. package/.cursor/hooks.json +107 -0
  35. package/.cursor/skills/aside/SKILL.md +115 -0
  36. package/.cursor/skills/checkpoint/SKILL.md +50 -0
  37. package/.cursor/skills/configure-scc/SKILL.md +160 -0
  38. package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
  39. package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
  40. package/.cursor/skills/model-route/SKILL.md +81 -0
  41. package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
  42. package/.cursor/skills/refactor-clean/SKILL.md +133 -0
  43. package/.cursor/skills/resume-session/SKILL.md +111 -0
  44. package/.cursor/skills/save-session/SKILL.md +183 -0
  45. package/.cursor/skills/search-first/SKILL.md +140 -0
  46. package/.cursor/skills/security-scan/SKILL.md +142 -0
  47. package/.cursor/skills/sessions/SKILL.md +124 -0
  48. package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
  49. package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
  50. package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
  51. package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
  52. package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
  53. package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
  54. package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
  55. package/.cursor/skills/sf-api-design/SKILL.md +237 -0
  56. package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
  57. package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
  58. package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
  59. package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
  60. package/.cursor/skills/sf-debugging/SKILL.md +362 -0
  61. package/.cursor/skills/sf-deployment/SKILL.md +291 -0
  62. package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
  63. package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
  64. package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
  65. package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
  66. package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
  67. package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
  68. package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
  69. package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
  70. package/.cursor/skills/sf-help/SKILL.md +156 -0
  71. package/.cursor/skills/sf-integration/SKILL.md +479 -0
  72. package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
  73. package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
  74. package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
  75. package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
  76. package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
  77. package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
  78. package/.cursor/skills/sf-security/SKILL.md +330 -0
  79. package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
  80. package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
  81. package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
  82. package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
  83. package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
  84. package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
  85. package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
  86. package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
  87. package/.cursor/skills/strategic-compact/SKILL.md +205 -0
  88. package/.cursor/skills/update-docs/SKILL.md +162 -0
  89. package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
  90. package/.cursor-plugin/plugin.json +26 -0
  91. package/LICENSE +21 -0
  92. package/README.md +522 -0
  93. package/agents/deep-researcher.md +145 -0
  94. package/agents/doc-updater.md +222 -0
  95. package/agents/eval-runner.md +340 -0
  96. package/agents/learning-engine.md +211 -0
  97. package/agents/loop-operator.md +247 -0
  98. package/agents/refactor-cleaner.md +122 -0
  99. package/agents/sf-admin-agent.md +131 -0
  100. package/agents/sf-agentforce-agent.md +132 -0
  101. package/agents/sf-apex-agent.md +124 -0
  102. package/agents/sf-architect.md +435 -0
  103. package/agents/sf-aura-reviewer.md +372 -0
  104. package/agents/sf-bugfix-agent.md +105 -0
  105. package/agents/sf-flow-agent.md +159 -0
  106. package/agents/sf-integration-agent.md +146 -0
  107. package/agents/sf-lwc-agent.md +127 -0
  108. package/agents/sf-review-agent.md +366 -0
  109. package/agents/sf-visualforce-reviewer.md +468 -0
  110. package/assets/logo.svg +18 -0
  111. package/docs/ARCHITECTURE.md +133 -0
  112. package/docs/authoring-guide.md +373 -0
  113. package/docs/hook-development.md +578 -0
  114. package/docs/token-optimization.md +139 -0
  115. package/docs/workflow-examples.md +645 -0
  116. package/examples/agentforce-action/README.md +227 -0
  117. package/examples/apex-trigger-handler/README.md +114 -0
  118. package/examples/devops-pipeline/README.md +325 -0
  119. package/examples/flow-automation/README.md +188 -0
  120. package/examples/integration-pattern/README.md +416 -0
  121. package/examples/lwc-component/README.md +180 -0
  122. package/examples/platform-events/README.md +492 -0
  123. package/examples/scratch-org-setup/README.md +138 -0
  124. package/examples/security-audit/README.md +244 -0
  125. package/examples/visualforce-migration/README.md +314 -0
  126. package/hooks/hooks.json +338 -0
  127. package/hooks/memory-persistence/README.md +73 -0
  128. package/manifests/install-modules.json +217 -0
  129. package/manifests/install-profiles.json +17 -0
  130. package/mcp-configs/mcp-servers.json +19 -0
  131. package/package.json +89 -0
  132. package/schemas/hooks.schema.json +123 -0
  133. package/schemas/install-modules.schema.json +76 -0
  134. package/schemas/install-profiles.schema.json +28 -0
  135. package/schemas/install-state.schema.json +73 -0
  136. package/schemas/package-manager.schema.json +18 -0
  137. package/schemas/plugin.schema.json +112 -0
  138. package/schemas/scc-install-config.schema.json +29 -0
  139. package/schemas/state-store.schema.json +111 -0
  140. package/scripts/cli/install-apply.js +170 -0
  141. package/scripts/cli/uninstall.js +193 -0
  142. package/scripts/hooks/check-console-log.js +101 -0
  143. package/scripts/hooks/check-hook-enabled.js +17 -0
  144. package/scripts/hooks/check-platform-docs-age.js +48 -0
  145. package/scripts/hooks/cost-tracker.js +78 -0
  146. package/scripts/hooks/doc-file-warning.js +63 -0
  147. package/scripts/hooks/evaluate-session.js +98 -0
  148. package/scripts/hooks/governor-check.js +220 -0
  149. package/scripts/hooks/learning-observe.sh +206 -0
  150. package/scripts/hooks/mcp-health-check.js +588 -0
  151. package/scripts/hooks/post-bash-build-complete.js +34 -0
  152. package/scripts/hooks/post-bash-pr-created.js +43 -0
  153. package/scripts/hooks/post-edit-console-warn.js +61 -0
  154. package/scripts/hooks/post-edit-format.js +79 -0
  155. package/scripts/hooks/post-edit-typecheck.js +98 -0
  156. package/scripts/hooks/post-write.js +168 -0
  157. package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
  158. package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
  159. package/scripts/hooks/pre-compact.js +51 -0
  160. package/scripts/hooks/pre-tool-use.js +163 -0
  161. package/scripts/hooks/pre-write-doc-warn.js +9 -0
  162. package/scripts/hooks/quality-gate.js +251 -0
  163. package/scripts/hooks/run-with-flags-shell.sh +32 -0
  164. package/scripts/hooks/run-with-flags.js +135 -0
  165. package/scripts/hooks/session-end-marker.js +29 -0
  166. package/scripts/hooks/session-end.js +311 -0
  167. package/scripts/hooks/session-start.js +202 -0
  168. package/scripts/hooks/sfdx-scanner-check.js +142 -0
  169. package/scripts/hooks/sfdx-validate.js +119 -0
  170. package/scripts/hooks/stop-hook.js +170 -0
  171. package/scripts/hooks/suggest-compact.js +67 -0
  172. package/scripts/lib/agent-adapter.js +82 -0
  173. package/scripts/lib/apex-analysis.js +194 -0
  174. package/scripts/lib/hook-flags.js +74 -0
  175. package/scripts/lib/install-config.js +73 -0
  176. package/scripts/lib/install-executor.js +363 -0
  177. package/scripts/lib/install-state.js +121 -0
  178. package/scripts/lib/orchestration-session.js +299 -0
  179. package/scripts/lib/package-manager.js +124 -0
  180. package/scripts/lib/project-detect.js +228 -0
  181. package/scripts/lib/schema-validator.js +190 -0
  182. package/scripts/lib/skill-adapter.js +100 -0
  183. package/scripts/lib/state-store.js +376 -0
  184. package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
  185. package/scripts/lib/utils.js +313 -0
  186. package/scripts/scc.js +164 -0
  187. package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
  188. package/skills/_reference/APEX_CURSOR.md +159 -0
  189. package/skills/_reference/API_VERSIONS.md +78 -0
  190. package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
  191. package/skills/_reference/ASYNC_PATTERNS.md +163 -0
  192. package/skills/_reference/AURA_COMPONENTS.md +146 -0
  193. package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
  194. package/skills/_reference/DATA_MODELING.md +124 -0
  195. package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
  196. package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
  197. package/skills/_reference/DEPRECATIONS.md +79 -0
  198. package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
  199. package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
  200. package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
  201. package/skills/_reference/FLOW_PATTERNS.md +113 -0
  202. package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
  203. package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
  204. package/skills/_reference/LWC_PATTERNS.md +79 -0
  205. package/skills/_reference/METADATA_TYPES.md +115 -0
  206. package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
  207. package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
  208. package/skills/_reference/PLATFORM_EVENTS.md +121 -0
  209. package/skills/_reference/REPORTING_API.md +143 -0
  210. package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
  211. package/skills/_reference/SECURITY_PATTERNS.md +127 -0
  212. package/skills/_reference/SHARING_MODEL.md +120 -0
  213. package/skills/_reference/SOQL_PATTERNS.md +119 -0
  214. package/skills/_reference/TESTING_STANDARDS.md +96 -0
  215. package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
  216. package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
  217. package/skills/aside/SKILL.md +118 -0
  218. package/skills/checkpoint/SKILL.md +53 -0
  219. package/skills/configure-scc/SKILL.md +163 -0
  220. package/skills/continuous-agent-loop/SKILL.md +264 -0
  221. package/skills/mcp-server-patterns/SKILL.md +146 -0
  222. package/skills/model-route/SKILL.md +84 -0
  223. package/skills/prompt-optimizer/SKILL.md +369 -0
  224. package/skills/refactor-clean/SKILL.md +136 -0
  225. package/skills/resume-session/SKILL.md +114 -0
  226. package/skills/save-session/SKILL.md +186 -0
  227. package/skills/search-first/SKILL.md +144 -0
  228. package/skills/security-scan/SKILL.md +146 -0
  229. package/skills/sessions/SKILL.md +127 -0
  230. package/skills/sf-agentforce-development/SKILL.md +450 -0
  231. package/skills/sf-apex-async-patterns/SKILL.md +326 -0
  232. package/skills/sf-apex-best-practices/SKILL.md +425 -0
  233. package/skills/sf-apex-constraints/SKILL.md +81 -0
  234. package/skills/sf-apex-cursor/SKILL.md +338 -0
  235. package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
  236. package/skills/sf-apex-testing/SKILL.md +409 -0
  237. package/skills/sf-api-design/SKILL.md +238 -0
  238. package/skills/sf-approval-processes/SKILL.md +315 -0
  239. package/skills/sf-aura-development/SKILL.md +263 -0
  240. package/skills/sf-build-fix/SKILL.md +121 -0
  241. package/skills/sf-data-modeling/SKILL.md +278 -0
  242. package/skills/sf-debugging/SKILL.md +363 -0
  243. package/skills/sf-deployment/SKILL.md +295 -0
  244. package/skills/sf-deployment-constraints/SKILL.md +155 -0
  245. package/skills/sf-devops-ci-cd/SKILL.md +325 -0
  246. package/skills/sf-docs-lookup/SKILL.md +103 -0
  247. package/skills/sf-e2e-testing/SKILL.md +324 -0
  248. package/skills/sf-experience-cloud/SKILL.md +249 -0
  249. package/skills/sf-flow-development/SKILL.md +377 -0
  250. package/skills/sf-governor-limits/SKILL.md +323 -0
  251. package/skills/sf-harness-audit/SKILL.md +142 -0
  252. package/skills/sf-help/SKILL.md +159 -0
  253. package/skills/sf-integration/SKILL.md +483 -0
  254. package/skills/sf-lwc-constraints/SKILL.md +130 -0
  255. package/skills/sf-lwc-development/SKILL.md +303 -0
  256. package/skills/sf-lwc-testing/SKILL.md +388 -0
  257. package/skills/sf-metadata-management/SKILL.md +288 -0
  258. package/skills/sf-platform-events-cdc/SKILL.md +375 -0
  259. package/skills/sf-quickstart/SKILL.md +173 -0
  260. package/skills/sf-security/SKILL.md +334 -0
  261. package/skills/sf-security-constraints/SKILL.md +127 -0
  262. package/skills/sf-soql-constraints/SKILL.md +131 -0
  263. package/skills/sf-soql-optimization/SKILL.md +354 -0
  264. package/skills/sf-tdd-workflow/SKILL.md +336 -0
  265. package/skills/sf-testing-constraints/SKILL.md +200 -0
  266. package/skills/sf-trigger-constraints/SKILL.md +90 -0
  267. package/skills/sf-trigger-frameworks/SKILL.md +347 -0
  268. package/skills/sf-visualforce-development/SKILL.md +260 -0
  269. package/skills/strategic-compact/SKILL.md +208 -0
  270. package/skills/update-docs/SKILL.md +165 -0
  271. package/skills/update-platform-docs/SKILL.md +90 -0
@@ -0,0 +1,180 @@
1
+ # LWC Component Example
2
+
3
+ Sample Lightning Web Component with wire service, Apex calls, and Jest tests.
4
+
5
+ ## Structure
6
+
7
+ ```text
8
+ force-app/main/default/lwc/
9
+ accountList/
10
+ accountList.html
11
+ accountList.js
12
+ accountList.js-meta.xml
13
+ accountList.css
14
+ __tests__/
15
+ accountList.test.js
16
+ ```
17
+
18
+ ## Component (HTML)
19
+
20
+ ```html
21
+ <template>
22
+ <lightning-card title="Accounts" icon-name="standard:account">
23
+ <template if:true={accounts.data}>
24
+ <lightning-datatable
25
+ key-field="Id"
26
+ data={accounts.data}
27
+ columns={columns}
28
+ onrowaction={handleRowAction}>
29
+ </lightning-datatable>
30
+ </template>
31
+ <template if:true={accounts.error}>
32
+ <p class="slds-text-color_error">Error loading accounts: {errorMessage}</p>
33
+ </template>
34
+ <template if:false={accounts.data}>
35
+ <lightning-spinner alternative-text="Loading"></lightning-spinner>
36
+ </template>
37
+ </lightning-card>
38
+ </template>
39
+ ```
40
+
41
+ ## Component (JS)
42
+
43
+ ```javascript
44
+ import { LightningElement, wire } from 'lwc';
45
+ import getAccounts from '@salesforce/apex/AccountController.getAccounts';
46
+
47
+ const COLUMNS = [
48
+ { label: 'Name', fieldName: 'Name', type: 'text' },
49
+ { label: 'Industry', fieldName: 'Industry', type: 'text' },
50
+ { label: 'Revenue', fieldName: 'AnnualRevenue', type: 'currency' },
51
+ {
52
+ type: 'action',
53
+ typeAttributes: { rowActions: [{ label: 'View', name: 'view' }] }
54
+ }
55
+ ];
56
+
57
+ export default class AccountList extends LightningElement {
58
+ columns = COLUMNS;
59
+
60
+ @wire(getAccounts)
61
+ accounts;
62
+
63
+ get errorMessage() {
64
+ return this.accounts?.error?.body?.message || 'Unknown error';
65
+ }
66
+
67
+ handleRowAction(event) {
68
+ const { action, row } = event.detail;
69
+ if (action.name === 'view') {
70
+ this.dispatchEvent(new CustomEvent('viewaccount', { detail: { accountId: row.Id } }));
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Component Metadata
77
+
78
+ ```xml
79
+ <!-- accountList.js-meta.xml -->
80
+ <?xml version="1.0" encoding="UTF-8"?>
81
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
82
+ <apiVersion>66.0</apiVersion>
83
+ <isExposed>true</isExposed>
84
+ <targets>
85
+ <target>lightning__RecordPage</target>
86
+ <target>lightning__AppPage</target>
87
+ <target>lightning__HomePage</target>
88
+ </targets>
89
+ <targetConfigs>
90
+ <targetConfig targets="lightning__RecordPage">
91
+ <objects>
92
+ <object>Account</object>
93
+ </objects>
94
+ </targetConfig>
95
+ </targetConfigs>
96
+ </LightningComponentBundle>
97
+ ```
98
+
99
+ ## Jest Tests
100
+
101
+ ```javascript
102
+ import { createElement } from 'lwc';
103
+ import AccountList from 'c/accountList';
104
+ import getAccounts from '@salesforce/apex/AccountController.getAccounts';
105
+
106
+ // Standard @salesforce/sfdx-lwc-jest mock pattern
107
+ jest.mock(
108
+ '@salesforce/apex/AccountController.getAccounts',
109
+ () => ({ default: jest.fn() }),
110
+ { virtual: true }
111
+ );
112
+
113
+ const MOCK_ACCOUNTS = [
114
+ { Id: '001xx001', Name: 'Acme', Industry: 'Tech', AnnualRevenue: 100000 },
115
+ { Id: '001xx002', Name: 'Global', Industry: 'Finance', AnnualRevenue: 500000 },
116
+ ];
117
+
118
+ describe('c-account-list', () => {
119
+ afterEach(() => {
120
+ while (document.body.firstChild) {
121
+ document.body.removeChild(document.body.firstChild);
122
+ }
123
+ jest.clearAllMocks();
124
+ });
125
+
126
+ it('renders datatable when data is available', async () => {
127
+ getAccounts.mockResolvedValue(MOCK_ACCOUNTS);
128
+ const element = createElement('c-account-list', { is: AccountList });
129
+ document.body.appendChild(element);
130
+
131
+ await Promise.resolve();
132
+ await Promise.resolve();
133
+
134
+ const datatable = element.shadowRoot.querySelector('lightning-datatable');
135
+ expect(datatable).not.toBeNull();
136
+ });
137
+
138
+ it('shows error message on failure', async () => {
139
+ getAccounts.mockRejectedValue({ body: { message: 'Test error' } });
140
+ const element = createElement('c-account-list', { is: AccountList });
141
+ document.body.appendChild(element);
142
+
143
+ await Promise.resolve();
144
+ await Promise.resolve();
145
+
146
+ const error = element.shadowRoot.querySelector('.slds-text-color_error');
147
+ expect(error).not.toBeNull();
148
+ expect(error.textContent).toContain('Test error');
149
+ });
150
+
151
+ it('dispatches viewaccount event on row action', async () => {
152
+ getAccounts.mockResolvedValue(MOCK_ACCOUNTS);
153
+ const element = createElement('c-account-list', { is: AccountList });
154
+ const handler = jest.fn();
155
+ element.addEventListener('viewaccount', handler);
156
+ document.body.appendChild(element);
157
+
158
+ await Promise.resolve();
159
+ await Promise.resolve();
160
+
161
+ const datatable = element.shadowRoot.querySelector('lightning-datatable');
162
+ datatable.dispatchEvent(
163
+ new CustomEvent('rowaction', {
164
+ detail: { action: { name: 'view' }, row: MOCK_ACCOUNTS[0] }
165
+ })
166
+ );
167
+
168
+ expect(handler).toHaveBeenCalledTimes(1);
169
+ expect(handler.mock.calls[0][0].detail.accountId).toBe('001xx001');
170
+ });
171
+
172
+ it('shows spinner when data is loading', () => {
173
+ const element = createElement('c-account-list', { is: AccountList });
174
+ document.body.appendChild(element);
175
+
176
+ const spinner = element.shadowRoot.querySelector('lightning-spinner');
177
+ expect(spinner).not.toBeNull();
178
+ });
179
+ });
180
+ ```
@@ -0,0 +1,492 @@
1
+ # Platform Events with Change Data Capture
2
+
3
+ Implementing Platform Events for real-time event-driven architecture and subscribing to Change Data Capture events in LWC.
4
+
5
+ ## When to Use This Pattern
6
+
7
+ - Building real-time notifications between Salesforce components or external systems
8
+ - Reacting to record changes across the org without triggers on every object
9
+ - Decoupling publishers from subscribers for loosely coupled integrations
10
+ - Streaming data changes to Lightning components for live UI updates
11
+
12
+ ## Structure
13
+
14
+ ```text
15
+ force-app/main/default/
16
+ objects/
17
+ Order_Status_Event__e/
18
+ Order_Status_Event__e.object-meta.xml # Platform Event definition
19
+ fields/
20
+ Order_Id__c.field-meta.xml
21
+ Status__c.field-meta.xml
22
+ Message__c.field-meta.xml
23
+ classes/
24
+ OrderEventPublisher.cls # Publishes events from Apex
25
+ OrderEventPublisher_Test.cls
26
+ OrderEventSubscriber.cls # Trigger subscriber
27
+ OrderEventSubscriber_Test.cls
28
+ triggers/
29
+ OrderStatusEventTrigger.trigger # Event trigger
30
+ lwc/
31
+ orderStatusMonitor/ # LWC subscriber via empApi
32
+ ```
33
+
34
+ ## Platform Event Definition
35
+
36
+ ```xml
37
+ <!-- Order_Status_Event__e.object-meta.xml -->
38
+ <?xml version="1.0" encoding="UTF-8"?>
39
+ <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
40
+ <deploymentStatus>Deployed</deploymentStatus>
41
+ <description>Published when an order status changes. Subscribers include
42
+ UI components and integration middleware.</description>
43
+ <eventType>HighVolume</eventType>
44
+ <label>Order Status Event</label>
45
+ <pluralLabel>Order Status Events</pluralLabel>
46
+ <publishBehavior>PublishAfterCommit</publishBehavior>
47
+ </CustomObject>
48
+ ```
49
+
50
+ ```xml
51
+ <!-- Order_Id__c.field-meta.xml -->
52
+ <?xml version="1.0" encoding="UTF-8"?>
53
+ <CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
54
+ <fullName>Order_Id__c</fullName>
55
+ <label>Order ID</label>
56
+ <length>18</length>
57
+ <type>Text</type>
58
+ <required>true</required>
59
+ <description>The Salesforce ID of the order that changed status</description>
60
+ </CustomField>
61
+ ```
62
+
63
+ ## Publishing Events from Apex
64
+
65
+ ```apex
66
+ public with sharing class OrderEventPublisher {
67
+
68
+ /**
69
+ * Publishes order status change events. Uses PublishAfterCommit so events
70
+ * are only delivered if the enclosing transaction succeeds.
71
+ */
72
+ public static void publishStatusChange(Id orderId, String newStatus, String message) {
73
+ Order_Status_Event__e event = new Order_Status_Event__e(
74
+ Order_Id__c = orderId,
75
+ Status__c = newStatus,
76
+ Message__c = message
77
+ );
78
+
79
+ Database.SaveResult result = EventBus.publish(event);
80
+
81
+ if (!result.isSuccess()) {
82
+ for (Database.Error err : result.getErrors()) {
83
+ System.debug(LoggingLevel.ERROR,
84
+ 'Order event publish failed: ' + err.getStatusCode() + ' - ' + err.getMessage()
85
+ );
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Bulk publish for batch processes. Publishes up to 10,000 events per call.
92
+ */
93
+ public static List<Database.SaveResult> publishBulkStatusChanges(
94
+ List<Order_Status_Event__e> events
95
+ ) {
96
+ return EventBus.publish(events);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Subscribing via Apex Trigger
102
+
103
+ ```apex
104
+ // OrderStatusEventTrigger.trigger
105
+ trigger OrderStatusEventTrigger on Order_Status_Event__e (after insert) {
106
+ OrderEventSubscriber.handleEvents(Trigger.new);
107
+ }
108
+ ```
109
+
110
+ ```apex
111
+ public with sharing class OrderEventSubscriber {
112
+
113
+ /**
114
+ * Processes incoming order status events. Updates related records and
115
+ * creates tasks for critical status changes. Uses
116
+ * EventBus.TriggerContext.currentContext().setResumeCheckpoint to
117
+ * handle large event volumes gracefully.
118
+ */
119
+ public static void handleEvents(List<Order_Status_Event__e> events) {
120
+ Set<String> orderIdStrings = new Set<String>();
121
+ Map<String, Order_Status_Event__e> latestByOrder = new Map<String, Order_Status_Event__e>();
122
+
123
+ for (Order_Status_Event__e evt : events) {
124
+ orderIdStrings.add(evt.Order_Id__c);
125
+ // Keep only the latest event per order (events arrive in order)
126
+ latestByOrder.put(evt.Order_Id__c, evt);
127
+ }
128
+
129
+ // Query orders to validate IDs exist — Order_Id__c is Text, so we must
130
+ // verify the referenced records are real before using them as WhatId
131
+ Map<Id, Order> ordersById = new Map<Id, Order>(
132
+ [SELECT Id FROM Order WHERE Id IN :orderIdStrings]
133
+ );
134
+
135
+ // Create follow-up tasks for failed orders
136
+ List<Task> tasks = new List<Task>();
137
+ for (Order_Status_Event__e evt : latestByOrder.values()) {
138
+ if (evt.Status__c == 'Failed') {
139
+ // Safely convert Text to Id — skip if the value is invalid
140
+ Id orderId;
141
+ try {
142
+ orderId = Id.valueOf(evt.Order_Id__c);
143
+ } catch (StringException e) {
144
+ System.debug(LoggingLevel.ERROR,
145
+ 'Invalid Order ID in event: ' + evt.Order_Id__c);
146
+ continue;
147
+ }
148
+
149
+ if (ordersById.containsKey(orderId)) {
150
+ tasks.add(new Task(
151
+ Subject = 'Order Failed: ' + evt.Order_Id__c,
152
+ Description = evt.Message__c,
153
+ WhatId = orderId,
154
+ Priority = 'High',
155
+ Status = 'Not Started',
156
+ ActivityDate = Date.today().addDays(1)
157
+ ));
158
+ }
159
+ }
160
+ }
161
+
162
+ if (!tasks.isEmpty()) {
163
+ insert tasks;
164
+ }
165
+
166
+ // Set checkpoint for replay on large batches
167
+ EventBus.TriggerContext.currentContext().setResumeCheckpoint(
168
+ events[events.size() - 1].ReplayId
169
+ );
170
+ }
171
+ }
172
+ ```
173
+
174
+ ## Subscribing in LWC via empApi
175
+
176
+ ```html
177
+ <!-- orderStatusMonitor.html -->
178
+ <template>
179
+ <lightning-card title="Order Status Monitor" icon-name="standard:orders">
180
+ <div class="slds-p-around_small">
181
+ <template if:true={isSubscribed}>
182
+ <lightning-badge label="Live" class="slds-m-bottom_small slds-theme_success"></lightning-badge>
183
+ </template>
184
+ <template if:false={isSubscribed}>
185
+ <lightning-badge label="Disconnected" class="slds-m-bottom_small"></lightning-badge>
186
+ <lightning-button label="Connect" onclick={handleSubscribe} variant="brand" class="slds-m-left_small"></lightning-button>
187
+ </template>
188
+ </div>
189
+
190
+ <template if:true={hasEvents}>
191
+ <lightning-datatable
192
+ key-field="replayId"
193
+ data={events}
194
+ columns={columns}
195
+ hide-checkbox-column>
196
+ </lightning-datatable>
197
+ </template>
198
+
199
+ <template if:false={hasEvents}>
200
+ <div class="slds-p-around_medium slds-text-align_center slds-text-color_weak">
201
+ Waiting for order status events...
202
+ </div>
203
+ </template>
204
+ </lightning-card>
205
+ </template>
206
+ ```
207
+
208
+ ```javascript
209
+ // orderStatusMonitor.js
210
+ import { LightningElement } from 'lwc';
211
+ import {
212
+ subscribe,
213
+ unsubscribe,
214
+ onError,
215
+ setDebugFlag,
216
+ isEmpEnabled
217
+ } from 'lightning/empApi';
218
+
219
+ const CHANNEL = '/event/Order_Status_Event__e';
220
+ const MAX_EVENTS = 50;
221
+
222
+ const COLUMNS = [
223
+ { label: 'Order ID', fieldName: 'orderId', type: 'text' },
224
+ { label: 'Status', fieldName: 'status', type: 'text' },
225
+ { label: 'Message', fieldName: 'message', type: 'text' },
226
+ { label: 'Time', fieldName: 'timestamp', type: 'text' }
227
+ ];
228
+
229
+ export default class OrderStatusMonitor extends LightningElement {
230
+ subscription = null;
231
+ events = [];
232
+ columns = COLUMNS;
233
+
234
+ get isSubscribed() {
235
+ return this.subscription != null;
236
+ }
237
+
238
+ get hasEvents() {
239
+ return this.events.length > 0;
240
+ }
241
+
242
+ connectedCallback() {
243
+ this.registerErrorListener();
244
+ this.handleSubscribe();
245
+ }
246
+
247
+ disconnectedCallback() {
248
+ this.handleUnsubscribe();
249
+ }
250
+
251
+ handleSubscribe() {
252
+ // Subscribe with replay -1 (new events only) or -2 (all retained events)
253
+ const replayId = -1;
254
+
255
+ subscribe(CHANNEL, replayId, (response) => {
256
+ this.handleEvent(response);
257
+ }).then((sub) => {
258
+ this.subscription = sub;
259
+ console.log('Subscribed to ' + CHANNEL);
260
+ });
261
+ }
262
+
263
+ handleUnsubscribe() {
264
+ if (this.subscription) {
265
+ unsubscribe(this.subscription, () => {
266
+ this.subscription = null;
267
+ console.log('Unsubscribed from ' + CHANNEL);
268
+ });
269
+ }
270
+ }
271
+
272
+ handleEvent(response) {
273
+ const payload = response.data.payload;
274
+ const newEvent = {
275
+ replayId: response.data.event.replayId,
276
+ orderId: payload.Order_Id__c,
277
+ status: payload.Status__c,
278
+ message: payload.Message__c,
279
+ timestamp: new Date(payload.CreatedDate).toLocaleString()
280
+ };
281
+
282
+ // Prepend new event; keep only the most recent MAX_EVENTS
283
+ this.events = [newEvent, ...this.events].slice(0, MAX_EVENTS);
284
+ }
285
+
286
+ registerErrorListener() {
287
+ onError((error) => {
288
+ console.error('empApi error: ', JSON.stringify(error));
289
+ // Reset subscription state so user can reconnect
290
+ this.subscription = null;
291
+ });
292
+ }
293
+ }
294
+ ```
295
+
296
+ ## Change Data Capture Subscription
297
+
298
+ Change Data Capture (CDC) publishes events automatically when records are created, updated, deleted, or undeleted. No custom event definition is needed.
299
+
300
+ ```javascript
301
+ // accountChangeMonitor.js
302
+ import { LightningElement } from 'lwc';
303
+ import { subscribe, unsubscribe, onError } from 'lightning/empApi';
304
+
305
+ // CDC channel format: /data/<ObjectName>ChangeEvent
306
+ const CDC_CHANNEL = '/data/AccountChangeEvent';
307
+
308
+ export default class AccountChangeMonitor extends LightningElement {
309
+ subscription = null;
310
+ changes = [];
311
+
312
+ connectedCallback() {
313
+ this.registerErrorListener();
314
+ subscribe(CDC_CHANNEL, -1, (response) => {
315
+ this.handleChangeEvent(response);
316
+ }).then((sub) => {
317
+ this.subscription = sub;
318
+ });
319
+ }
320
+
321
+ disconnectedCallback() {
322
+ if (this.subscription) {
323
+ unsubscribe(this.subscription, () => {
324
+ this.subscription = null;
325
+ });
326
+ }
327
+ }
328
+
329
+ handleChangeEvent(response) {
330
+ const header = response.data.payload.ChangeEventHeader;
331
+ const change = {
332
+ id: Date.now(),
333
+ recordIds: header.recordIds.join(', '),
334
+ changeType: header.changeType, // CREATE, UPDATE, DELETE, UNDELETE
335
+ changedFields: header.changedFields.join(', '),
336
+ commitUser: header.commitUser,
337
+ timestamp: new Date(header.commitTimestamp).toLocaleString()
338
+ };
339
+
340
+ this.changes = [change, ...this.changes].slice(0, 100);
341
+ }
342
+
343
+ registerErrorListener() {
344
+ onError((error) => {
345
+ console.error('CDC error: ', JSON.stringify(error));
346
+ this.subscription = null;
347
+ });
348
+ }
349
+ }
350
+ ```
351
+
352
+ ## Error Handling and Replay
353
+
354
+ ```apex
355
+ // Handling publish failures with retry
356
+ public with sharing class EventPublishHelper {
357
+
358
+ private static final Integer MAX_RETRIES = 3;
359
+
360
+ public static void publishWithRetry(List<Order_Status_Event__e> events) {
361
+ Integer attempts = 0;
362
+ List<Order_Status_Event__e> failedEvents = new List<Order_Status_Event__e>(events);
363
+
364
+ while (!failedEvents.isEmpty() && attempts < MAX_RETRIES) {
365
+ attempts++;
366
+ List<Database.SaveResult> results = EventBus.publish(failedEvents);
367
+ failedEvents = new List<Order_Status_Event__e>();
368
+
369
+ for (Integer i = 0; i < results.size(); i++) {
370
+ if (!results[i].isSuccess()) {
371
+ failedEvents.add(events[i]);
372
+ for (Database.Error err : results[i].getErrors()) {
373
+ System.debug(LoggingLevel.WARN,
374
+ 'Publish attempt ' + attempts + ' failed for event ' + i
375
+ + ': ' + err.getMessage()
376
+ );
377
+ }
378
+ }
379
+ }
380
+ }
381
+
382
+ if (!failedEvents.isEmpty()) {
383
+ System.debug(LoggingLevel.ERROR,
384
+ failedEvents.size() + ' events failed after ' + MAX_RETRIES + ' attempts'
385
+ );
386
+ // Log to a custom object or send an alert
387
+ }
388
+ }
389
+ }
390
+ ```
391
+
392
+ ## Test Class
393
+
394
+ ```apex
395
+ @IsTest
396
+ private class OrderEventPublisher_Test {
397
+
398
+ @IsTest
399
+ static void testPublishSingleEvent() {
400
+ Test.startTest();
401
+ OrderEventPublisher.publishStatusChange(
402
+ '801xx000000001AAA',
403
+ 'Shipped',
404
+ 'Order has been shipped via FedEx'
405
+ );
406
+ Test.stopTest();
407
+
408
+ // Verify event was published (query the event bus in test context)
409
+ // Platform Events published in tests are immediately available after Test.stopTest()
410
+ }
411
+
412
+ @IsTest
413
+ static void testPublishBulkEvents() {
414
+ List<Order_Status_Event__e> events = new List<Order_Status_Event__e>();
415
+ for (Integer i = 0; i < 200; i++) {
416
+ events.add(new Order_Status_Event__e(
417
+ Order_Id__c = '801xx00000000' + String.valueOf(i).leftPad(4, '0'),
418
+ Status__c = 'Processing',
419
+ Message__c = 'Bulk event ' + i
420
+ ));
421
+ }
422
+
423
+ Test.startTest();
424
+ List<Database.SaveResult> results = OrderEventPublisher.publishBulkStatusChanges(events);
425
+ Test.stopTest();
426
+
427
+ for (Database.SaveResult result : results) {
428
+ System.assert(result.isSuccess(), 'Event publish should succeed');
429
+ }
430
+ }
431
+
432
+ @IsTest
433
+ static void testSubscriberCreatesTasksForFailedOrders() {
434
+ // Create a real Order so the subscriber can validate the ID via SOQL
435
+ Account acc = new Account(Name = 'Test Account');
436
+ insert acc;
437
+ Order ord = new Order(
438
+ AccountId = acc.Id,
439
+ EffectiveDate = Date.today(),
440
+ Status = 'Draft'
441
+ );
442
+ insert ord;
443
+
444
+ List<Order_Status_Event__e> events = new List<Order_Status_Event__e>{
445
+ new Order_Status_Event__e(
446
+ Order_Id__c = ord.Id,
447
+ Status__c = 'Failed',
448
+ Message__c = 'Payment declined'
449
+ ),
450
+ new Order_Status_Event__e(
451
+ Order_Id__c = ord.Id,
452
+ Status__c = 'Shipped',
453
+ Message__c = 'Shipped successfully'
454
+ )
455
+ };
456
+
457
+ Test.startTest();
458
+ OrderEventSubscriber.handleEvents(events);
459
+ Test.stopTest();
460
+
461
+ List<Task> tasks = [SELECT Subject, Priority, WhatId FROM Task WHERE Subject LIKE 'Order Failed%'];
462
+ System.assertEquals(1, tasks.size(), 'Should create task only for failed order');
463
+ System.assertEquals('High', tasks[0].Priority);
464
+ System.assertEquals(ord.Id, tasks[0].WhatId, 'Task should be linked to the Order');
465
+ }
466
+ }
467
+ ```
468
+
469
+ ## Key Principles
470
+
471
+ - Use `PublishAfterCommit` to ensure events are only delivered when the transaction succeeds
472
+ - Use `PublishImmediately` only when events must fire regardless of transaction outcome
473
+ - Set resume checkpoints in event triggers to handle large event volumes without hitting limits
474
+ - Subscribe with replay ID `-1` for new events only, or `-2` to replay all retained events (up to 72 hours)
475
+ - Keep event payloads small: include IDs and status, not full record data
476
+ - CDC events are automatically generated; use them instead of custom events when you only need record change notifications
477
+
478
+ ## Common Pitfalls
479
+
480
+ - Publishing events inside a loop instead of collecting and bulk-publishing
481
+ - Forgetting that `PublishAfterCommit` events are lost if the transaction rolls back (this is the intended behavior)
482
+ - Not handling the `onError` callback in empApi, which causes silent subscription failures
483
+ - Exceeding the daily Platform Event allocation (check org limits)
484
+ - Assuming event delivery order is guaranteed across different event types
485
+ - Not calling `unsubscribe` in `disconnectedCallback`, which leaks subscriptions
486
+
487
+ ## SCC Skills
488
+
489
+ - `/sf-platform-events-cdc` -- review Platform Event and CDC implementations
490
+ - `/sf-apex-best-practices` -- review publisher and subscriber Apex code
491
+ - `/sf-lwc-development` -- review the empApi subscription component
492
+ - `/sf-governor-limits` -- check event publish limits and bulk compliance