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.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-trigger-frameworks
|
|
3
|
+
description: >-
|
|
4
|
+
Salesforce Apex trigger framework patterns — TriggerHandler, FFLIB Domain,
|
|
5
|
+
TDTM, bypass and recursion control. Use when adopting or refactoring triggers.
|
|
6
|
+
origin: SCC
|
|
7
|
+
user-invocable: false
|
|
8
|
+
allowed-tools: Read, Grep, Glob, Edit, Write, Bash
|
|
9
|
+
disable-model-invocation: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Trigger Frameworks
|
|
13
|
+
|
|
14
|
+
Implementation guidance for trigger framework patterns. Constraint rules (one-trigger-per-object, no-logic-in-trigger-body, etc.) live in `sf-trigger-constraints`. This skill covers the _how_ — framework selection, base class implementation, bypass mechanisms, and recursion prevention.
|
|
15
|
+
|
|
16
|
+
Reference: @../_reference/TRIGGER_PATTERNS.md
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
|
|
22
|
+
- When evaluating which trigger framework pattern to adopt for a new project
|
|
23
|
+
- When refactoring bare trigger logic to a handler-based architecture
|
|
24
|
+
- When a trigger is firing recursively and causing unexpected DML or loop errors
|
|
25
|
+
- When you need a bypass mechanism to suppress triggers during data migrations
|
|
26
|
+
- When multiple triggers exist on the same SObject and need to be consolidated
|
|
27
|
+
- When comparing Pragmatic TriggerHandler vs FFLIB Domain Layer
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The Pragmatic TriggerHandler Pattern
|
|
32
|
+
|
|
33
|
+
A clean, dependency-free framework. The base class provides context-aware routing, bypass mechanism, and recursion control.
|
|
34
|
+
|
|
35
|
+
### TriggerHandler Base Class
|
|
36
|
+
|
|
37
|
+
```apex
|
|
38
|
+
public virtual class TriggerHandler {
|
|
39
|
+
|
|
40
|
+
// Bypass Registry
|
|
41
|
+
private static Set<String> bypassedHandlers = new Set<String>();
|
|
42
|
+
|
|
43
|
+
// Recursion Control
|
|
44
|
+
private static Map<String, Integer> depthMap = new Map<String, Integer>();
|
|
45
|
+
private static final Integer MAX_DEPTH = 2;
|
|
46
|
+
|
|
47
|
+
// Context Properties
|
|
48
|
+
@TestVisible protected Boolean isBefore { get { return Trigger.isBefore; } }
|
|
49
|
+
@TestVisible protected Boolean isAfter { get { return Trigger.isAfter; } }
|
|
50
|
+
@TestVisible protected Boolean isInsert { get { return Trigger.isInsert; } }
|
|
51
|
+
@TestVisible protected Boolean isUpdate { get { return Trigger.isUpdate; } }
|
|
52
|
+
@TestVisible protected Boolean isDelete { get { return Trigger.isDelete; } }
|
|
53
|
+
@TestVisible protected Boolean isUndelete { get { return Trigger.isUndelete; } }
|
|
54
|
+
|
|
55
|
+
protected List<SObject> newList { get { return Trigger.new; } }
|
|
56
|
+
protected Map<Id, SObject> newMap { get { return Trigger.newMap; } }
|
|
57
|
+
protected List<SObject> oldList { get { return Trigger.old; } }
|
|
58
|
+
protected Map<Id, SObject> oldMap { get { return Trigger.oldMap; } }
|
|
59
|
+
|
|
60
|
+
public void run() {
|
|
61
|
+
String handlerName = getHandlerName();
|
|
62
|
+
if (isBypassed(handlerName)) return;
|
|
63
|
+
if (exceedsMaxDepth(handlerName)) return;
|
|
64
|
+
|
|
65
|
+
incrementDepth(handlerName);
|
|
66
|
+
try { dispatch(); }
|
|
67
|
+
finally { decrementDepth(handlerName); }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private void dispatch() {
|
|
71
|
+
if (isBefore) {
|
|
72
|
+
if (isInsert) onBeforeInsert();
|
|
73
|
+
if (isUpdate) onBeforeUpdate();
|
|
74
|
+
if (isDelete) onBeforeDelete();
|
|
75
|
+
} else if (isAfter) {
|
|
76
|
+
if (isInsert) onAfterInsert();
|
|
77
|
+
if (isUpdate) onAfterUpdate();
|
|
78
|
+
if (isDelete) onAfterDelete();
|
|
79
|
+
if (isUndelete) onAfterUndelete();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Virtual Methods — Override in Concrete Handlers
|
|
84
|
+
@TestVisible protected virtual void onBeforeInsert() {}
|
|
85
|
+
@TestVisible protected virtual void onBeforeUpdate() {}
|
|
86
|
+
@TestVisible protected virtual void onBeforeDelete() {}
|
|
87
|
+
@TestVisible protected virtual void onAfterInsert() {}
|
|
88
|
+
@TestVisible protected virtual void onAfterUpdate() {}
|
|
89
|
+
@TestVisible protected virtual void onAfterDelete() {}
|
|
90
|
+
@TestVisible protected virtual void onAfterUndelete() {}
|
|
91
|
+
|
|
92
|
+
// Bypass API
|
|
93
|
+
public static void bypass(String handlerName) { bypassedHandlers.add(handlerName); }
|
|
94
|
+
public static void clearBypass(String handlerName) { bypassedHandlers.remove(handlerName); }
|
|
95
|
+
public static void clearAllBypasses() { bypassedHandlers.clear(); }
|
|
96
|
+
public static Boolean isBypassed(String handlerName) { return bypassedHandlers.contains(handlerName); }
|
|
97
|
+
|
|
98
|
+
// Private Helpers
|
|
99
|
+
private String getHandlerName() { return String.valueOf(this).split(':')[0]; }
|
|
100
|
+
private Boolean exceedsMaxDepth(String h) { return getDepth(h) >= MAX_DEPTH; }
|
|
101
|
+
private Integer getDepth(String h) { return depthMap.containsKey(h) ? depthMap.get(h) : 0; }
|
|
102
|
+
private void incrementDepth(String h) { depthMap.put(h, getDepth(h) + 1); }
|
|
103
|
+
private void decrementDepth(String h) { Integer c = getDepth(h); if (c > 0) depthMap.put(h, c - 1); }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Concrete Handler
|
|
108
|
+
|
|
109
|
+
```apex
|
|
110
|
+
public class AccountTriggerHandler extends TriggerHandler {
|
|
111
|
+
|
|
112
|
+
private List<Account> newAccounts;
|
|
113
|
+
private Map<Id, Account> oldAccountMap;
|
|
114
|
+
|
|
115
|
+
public AccountTriggerHandler() {
|
|
116
|
+
newAccounts = (List<Account>) newList;
|
|
117
|
+
oldAccountMap = (Map<Id, Account>) oldMap;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override protected void onBeforeInsert() {
|
|
121
|
+
AccountDefaults.setDefaults(newAccounts);
|
|
122
|
+
AccountValidator.validateForInsert(newAccounts);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override protected void onBeforeUpdate() {
|
|
126
|
+
AccountValidator.validateForUpdate(newAccounts, oldAccountMap);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override protected void onAfterInsert() {
|
|
130
|
+
AccountOpportunityCreator.createDefaultOpportunities(newAccounts);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override protected void onAfterUpdate() {
|
|
134
|
+
AccountRelatedUpdater.syncContactOwnership(
|
|
135
|
+
(Map<Id, Account>) newMap, oldAccountMap
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Trigger File
|
|
142
|
+
|
|
143
|
+
```apex
|
|
144
|
+
trigger AccountTrigger on Account (
|
|
145
|
+
before insert, before update, before delete,
|
|
146
|
+
after insert, after update, after delete, after undelete
|
|
147
|
+
) {
|
|
148
|
+
new AccountTriggerHandler().run();
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## FFLIB Domain Layer
|
|
155
|
+
|
|
156
|
+
For orgs using the FFLIB Apex Commons library, the Domain layer is the preferred trigger handling mechanism.
|
|
157
|
+
|
|
158
|
+
```apex
|
|
159
|
+
public with sharing class Accounts extends fflib_SObjectDomain {
|
|
160
|
+
|
|
161
|
+
public Accounts(List<Account> sObjectList) {
|
|
162
|
+
super(sObjectList);
|
|
163
|
+
Configuration.disableTriggerCRUDSecurity();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public override void onBeforeInsert() {
|
|
167
|
+
setDefaultCustomerTier();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public override void onBeforeUpdate(Map<Id, SObject> existingRecords) {
|
|
171
|
+
preventPremiumTierDowngrade((Map<Id, Account>) existingRecords);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public class Constructor implements fflib_SObjectDomain.IConstructable {
|
|
175
|
+
public fflib_SObjectDomain construct(List<SObject> sObjectList) {
|
|
176
|
+
return new Accounts(sObjectList);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
> `Configuration.disableTriggerCRUDSecurity()` is needed because trigger handlers operate on records already committed by the platform. Do NOT disable CRUD security in Service or Controller layers.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## TDTM (Table-Driven Trigger Management)
|
|
187
|
+
|
|
188
|
+
Registers handlers in Custom Metadata (`Trigger_Handler__mdt`), enabling enable/disable without code deployment.
|
|
189
|
+
|
|
190
|
+
**CMDT fields:** `Object_Name__c`, `Handler_Class__c`, `Trigger_Event__c`, `Is_Active__c`, `Execution_Order__c`
|
|
191
|
+
|
|
192
|
+
```apex
|
|
193
|
+
public class TDTMDispatcher {
|
|
194
|
+
|
|
195
|
+
private static Map<String, List<Trigger_Handler__mdt>> handlerCache =
|
|
196
|
+
new Map<String, List<Trigger_Handler__mdt>>();
|
|
197
|
+
|
|
198
|
+
public static void run(
|
|
199
|
+
String objectName, String triggerEvent,
|
|
200
|
+
List<SObject> newList, List<SObject> oldList,
|
|
201
|
+
Map<Id, SObject> newMap, Map<Id, SObject> oldMap
|
|
202
|
+
) {
|
|
203
|
+
String cacheKey = objectName + ':' + triggerEvent;
|
|
204
|
+
List<Trigger_Handler__mdt> activeHandlers;
|
|
205
|
+
if (handlerCache.containsKey(cacheKey)) {
|
|
206
|
+
activeHandlers = handlerCache.get(cacheKey);
|
|
207
|
+
} else {
|
|
208
|
+
activeHandlers = [
|
|
209
|
+
SELECT Handler_Class__c, Execution_Order__c
|
|
210
|
+
FROM Trigger_Handler__mdt
|
|
211
|
+
WHERE Object_Name__c = :objectName
|
|
212
|
+
AND Trigger_Event__c = :triggerEvent
|
|
213
|
+
AND Is_Active__c = true
|
|
214
|
+
ORDER BY Execution_Order__c ASC
|
|
215
|
+
];
|
|
216
|
+
handlerCache.put(cacheKey, activeHandlers);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (Trigger_Handler__mdt cfg : activeHandlers) {
|
|
220
|
+
Type handlerType = Type.forName(cfg.Handler_Class__c);
|
|
221
|
+
if (handlerType == null) continue;
|
|
222
|
+
ITriggerHandler handler = (ITriggerHandler) handlerType.newInstance();
|
|
223
|
+
handler.execute(newList, oldList, newMap, oldMap);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public interface ITriggerHandler {
|
|
228
|
+
void execute(List<SObject> newList, List<SObject> oldList,
|
|
229
|
+
Map<Id, SObject> newMap, Map<Id, SObject> oldMap);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
To disable a handler for data migration: set `Is_Active__c = false` in Setup.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Bypass Mechanisms
|
|
239
|
+
|
|
240
|
+
### Static Boolean (Simple)
|
|
241
|
+
|
|
242
|
+
```apex
|
|
243
|
+
public class TriggerBypasses {
|
|
244
|
+
public static Boolean bypassAccountTrigger = false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Usage
|
|
248
|
+
TriggerBypasses.bypassAccountTrigger = true;
|
|
249
|
+
try {
|
|
250
|
+
insert accountsToMigrate;
|
|
251
|
+
} finally {
|
|
252
|
+
TriggerBypasses.bypassAccountTrigger = false;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Framework-Level (`TriggerHandler.bypass()`)
|
|
257
|
+
|
|
258
|
+
```apex
|
|
259
|
+
TriggerHandler.bypass('AccountTriggerHandler');
|
|
260
|
+
try {
|
|
261
|
+
insert accounts;
|
|
262
|
+
} finally {
|
|
263
|
+
TriggerHandler.clearBypass('AccountTriggerHandler');
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Custom Metadata Bypass (Declarative)
|
|
268
|
+
|
|
269
|
+
Map users/profiles to bypassed handlers via `Trigger_Bypass__mdt`. No code change needed.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Recursion Prevention
|
|
274
|
+
|
|
275
|
+
### Static Set of Processed IDs
|
|
276
|
+
|
|
277
|
+
```apex
|
|
278
|
+
public class AccountTriggerHandler extends TriggerHandler {
|
|
279
|
+
|
|
280
|
+
@TestVisible
|
|
281
|
+
private static Set<Id> processedIds = new Set<Id>();
|
|
282
|
+
|
|
283
|
+
override protected void onAfterUpdate() {
|
|
284
|
+
List<Account> unprocessed = new List<Account>();
|
|
285
|
+
for (Account acc : (List<Account>) newList) {
|
|
286
|
+
if (!processedIds.contains(acc.Id)) {
|
|
287
|
+
processedIds.add(acc.Id);
|
|
288
|
+
unprocessed.add(acc);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (!unprocessed.isEmpty()) {
|
|
292
|
+
AccountRelatedUpdater.syncContactOwnership(
|
|
293
|
+
new Map<Id, Account>(unprocessed), (Map<Id, Account>) oldMap
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
> **Testing note:** Static variables reset between test methods. Within a single test method, they persist across multiple trigger executions.
|
|
301
|
+
|
|
302
|
+
### Execution Depth Counter
|
|
303
|
+
|
|
304
|
+
Built into the TriggerHandler base class (MAX_DEPTH = 2). When a handler is called more than MAX_DEPTH times, execution is skipped. This prevents infinite recursion while allowing the first re-entry.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Migration Guide: Bare Trigger to Framework
|
|
309
|
+
|
|
310
|
+
1. Deploy `TriggerHandler.cls` base class
|
|
311
|
+
2. Create handler class: `public class AccountTriggerHandler extends TriggerHandler {}`
|
|
312
|
+
3. Refactor trigger: replace body with `new AccountTriggerHandler().run();`
|
|
313
|
+
4. Move logic method by method into handler overrides or service classes
|
|
314
|
+
5. Add bypass support via base class or Custom Metadata
|
|
315
|
+
6. Delete old trigger files after consolidation
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Testing Trigger Frameworks
|
|
320
|
+
|
|
321
|
+
```apex
|
|
322
|
+
@isTest
|
|
323
|
+
static void testBypassMechanism_noDefaultsSetWhenBypassed() {
|
|
324
|
+
TriggerHandler.bypass('AccountTriggerHandler');
|
|
325
|
+
Account acc = new Account(Name = 'Bypass Test', Type = 'Customer', Industry = 'Tech');
|
|
326
|
+
|
|
327
|
+
Test.startTest();
|
|
328
|
+
insert acc;
|
|
329
|
+
Test.stopTest();
|
|
330
|
+
|
|
331
|
+
TriggerHandler.clearBypass('AccountTriggerHandler');
|
|
332
|
+
|
|
333
|
+
Account result = [SELECT Customer_Tier__c FROM Account WHERE Id = :acc.Id];
|
|
334
|
+
System.assertEquals(null, result.Customer_Tier__c,
|
|
335
|
+
'Tier should NOT be set when handler is bypassed');
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Related
|
|
342
|
+
|
|
343
|
+
- **Agent**: `sf-architect` — For interactive, in-depth guidance
|
|
344
|
+
|
|
345
|
+
### Guardrails
|
|
346
|
+
|
|
347
|
+
- `sf-trigger-constraints` — Enforces one-trigger-per-object, handler delegation, bulkification, and recursion prevention rules
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-visualforce-development
|
|
3
|
+
description: "Visualforce development — pages, controllers, extensions, ViewState, JS Remoting, LWC migration. Use when maintaining VF pages, building PDFs, or planning VF-to-LWC migration. Do NOT use for LWC, Aura, or Flow."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Visualforce Development
|
|
9
|
+
|
|
10
|
+
Visualforce is Salesforce's server-side rendering framework. While LWC is the modern standard, Visualforce remains heavily used for PDF generation, email templates, custom overrides, and legacy applications.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- When maintaining or extending existing Visualforce pages in production
|
|
15
|
+
- When building PDF renderable pages (`renderAs="pdf"`) — LWC cannot do this
|
|
16
|
+
- When creating custom email templates with complex formatting
|
|
17
|
+
- When overriding standard buttons (New, Edit, View) with custom UIs
|
|
18
|
+
- When planning and executing migration from Visualforce to LWC
|
|
19
|
+
- When debugging ViewState issues or page performance problems
|
|
20
|
+
|
|
21
|
+
@../_reference/VISUALFORCE_PATTERNS.md
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Page Creation Procedure
|
|
26
|
+
|
|
27
|
+
### Step 1 — Choose Controller Type
|
|
28
|
+
|
|
29
|
+
| Type | When to Use |
|
|
30
|
+
|------|------------|
|
|
31
|
+
| Standard Controller | Single-record CRUD without custom logic |
|
|
32
|
+
| Standard List Controller | List views with built-in pagination |
|
|
33
|
+
| Custom Controller | Full control over logic, data, navigation |
|
|
34
|
+
| Controller Extension | Add functionality to standard/custom controllers |
|
|
35
|
+
|
|
36
|
+
### Step 2 — Create the Page
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<apex:page standardController="Account"
|
|
40
|
+
extensions="AccountOverviewExtension"
|
|
41
|
+
lightningStylesheets="true"
|
|
42
|
+
docType="html-5.0"
|
|
43
|
+
title="Account Overview">
|
|
44
|
+
<apex:pageBlock title="Account Details">
|
|
45
|
+
<apex:pageBlockSection columns="2">
|
|
46
|
+
<apex:outputField value="{!Account.Name}" />
|
|
47
|
+
<apex:outputField value="{!Account.Industry}" />
|
|
48
|
+
</apex:pageBlockSection>
|
|
49
|
+
</apex:pageBlock>
|
|
50
|
+
</apex:page>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 3 — Custom Controller (if needed)
|
|
54
|
+
|
|
55
|
+
```apex
|
|
56
|
+
public with sharing class InvoiceController {
|
|
57
|
+
public List<Invoice__c> invoices { get; private set; }
|
|
58
|
+
public String searchTerm { get; set; }
|
|
59
|
+
|
|
60
|
+
public InvoiceController() {
|
|
61
|
+
searchTerm = '';
|
|
62
|
+
loadInvoices();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public PageReference search() {
|
|
66
|
+
loadInvoices();
|
|
67
|
+
return null; // Stay on same page
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private void loadInvoices() {
|
|
71
|
+
String likeSearch = '%' + String.escapeSingleQuotes(searchTerm) + '%';
|
|
72
|
+
invoices = [
|
|
73
|
+
SELECT Id, Name, Amount__c, Status__c, CreatedDate
|
|
74
|
+
FROM Invoice__c WHERE Name LIKE :likeSearch
|
|
75
|
+
WITH USER_MODE ORDER BY CreatedDate DESC LIMIT 100
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 4 — Controller Extension (if needed)
|
|
82
|
+
|
|
83
|
+
```apex
|
|
84
|
+
public with sharing class AccountOverviewExtension {
|
|
85
|
+
private final Account account;
|
|
86
|
+
|
|
87
|
+
// Required constructor signature
|
|
88
|
+
public AccountOverviewExtension(ApexPages.StandardController stdController) {
|
|
89
|
+
if (!Test.isRunningTest()) {
|
|
90
|
+
stdController.addFields(new List<String>{ 'OwnerId', 'AnnualRevenue' });
|
|
91
|
+
}
|
|
92
|
+
this.account = (Account) stdController.getRecord();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public List<Contact> relatedContacts {
|
|
96
|
+
get {
|
|
97
|
+
if (relatedContacts == null) {
|
|
98
|
+
relatedContacts = [
|
|
99
|
+
SELECT Id, Name, Email, Phone
|
|
100
|
+
FROM Contact WHERE AccountId = :account.Id
|
|
101
|
+
WITH USER_MODE ORDER BY Name LIMIT 50
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
return relatedContacts;
|
|
105
|
+
}
|
|
106
|
+
private set;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## ViewState Management
|
|
114
|
+
|
|
115
|
+
ViewState is a hidden, encrypted form field that maintains page state across postbacks. **170KB limit** — exceeding it causes a runtime error.
|
|
116
|
+
|
|
117
|
+
### The `transient` Keyword
|
|
118
|
+
|
|
119
|
+
Mark variables that do not need to survive postbacks as `transient`:
|
|
120
|
+
|
|
121
|
+
```apex
|
|
122
|
+
public with sharing class ReportController {
|
|
123
|
+
// IN ViewState — needed across postbacks
|
|
124
|
+
public String selectedFilter { get; set; }
|
|
125
|
+
public Integer currentPage { get; set; }
|
|
126
|
+
|
|
127
|
+
// NOT in ViewState — recomputed on each request
|
|
128
|
+
transient public List<AggregateResult> reportData { get; private set; }
|
|
129
|
+
transient public Blob chartImage { get; private set; }
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Reduction Strategies
|
|
134
|
+
|
|
135
|
+
| Strategy | Impact |
|
|
136
|
+
|----------|--------|
|
|
137
|
+
| `transient` keyword on large/recomputable variables | High |
|
|
138
|
+
| `apex:outputPanel` + `reRender` (partial refresh) | Medium |
|
|
139
|
+
| Paginate large data sets | High |
|
|
140
|
+
| Use JavaScript Remoting (stateless) | High |
|
|
141
|
+
| Move read-only data outside `apex:form` | Medium |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## JavaScript Remoting
|
|
146
|
+
|
|
147
|
+
Stateless, high-performance Apex calls that bypass ViewState entirely.
|
|
148
|
+
|
|
149
|
+
### Apex Method
|
|
150
|
+
|
|
151
|
+
```apex
|
|
152
|
+
@RemoteAction
|
|
153
|
+
public static List<Account> findAccounts(String searchTerm) {
|
|
154
|
+
String safeTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
|
|
155
|
+
return [
|
|
156
|
+
SELECT Id, Name, Industry FROM Account
|
|
157
|
+
WHERE Name LIKE :safeTerm WITH USER_MODE LIMIT 25
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### JavaScript Invocation
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
Visualforce.remoting.Manager.invokeAction(
|
|
166
|
+
'{!$RemoteAction.AccountSearchController.findAccounts}',
|
|
167
|
+
term,
|
|
168
|
+
function(result, event) {
|
|
169
|
+
if (event.status) {
|
|
170
|
+
renderResults(result);
|
|
171
|
+
} else {
|
|
172
|
+
console.error(event.message);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{ escape: true, timeout: 30000 }
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Use `{!$RemoteAction.ClassName.methodName}` (namespace-safe). Set `escape: true` to prevent XSS.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Partial Page Refresh
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<apex:actionFunction name="refreshDashboard" action="{!refresh}"
|
|
187
|
+
reRender="dashPanel" status="loadingStatus" />
|
|
188
|
+
|
|
189
|
+
<apex:selectList value="{!selectedRegion}" size="1">
|
|
190
|
+
<apex:selectOptions value="{!regionOptions}" />
|
|
191
|
+
<apex:actionSupport event="onchange" action="{!filterByRegion}"
|
|
192
|
+
reRender="dashPanel" status="loadingStatus" />
|
|
193
|
+
</apex:selectList>
|
|
194
|
+
|
|
195
|
+
<apex:actionStatus id="loadingStatus">
|
|
196
|
+
<apex:facet name="start"><img src="/img/loading.gif" alt="Loading..." /></apex:facet>
|
|
197
|
+
</apex:actionStatus>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Migration to LWC
|
|
203
|
+
|
|
204
|
+
### Decision Matrix
|
|
205
|
+
|
|
206
|
+
| Keep Visualforce | Migrate to LWC |
|
|
207
|
+
|-----------------|----------------|
|
|
208
|
+
| PDF generation (`renderAs="pdf"`) | High-traffic pages needing performance |
|
|
209
|
+
| Email templates | New feature development |
|
|
210
|
+
| Complex server-state wizards | Pages using Apex controller only |
|
|
211
|
+
|
|
212
|
+
### Key VF-to-LWC Mappings
|
|
213
|
+
|
|
214
|
+
| Visualforce | LWC |
|
|
215
|
+
|------------|-----|
|
|
216
|
+
| `apex:pageBlockTable` | `lightning-datatable` |
|
|
217
|
+
| `apex:commandButton action="{!save}"` | `lightning-button onclick={handleSave}` + imperative Apex |
|
|
218
|
+
| `apex:inputField` | `lightning-input-field` (in `lightning-record-edit-form`) |
|
|
219
|
+
| JavaScript Remoting | `@wire` or imperative Apex import |
|
|
220
|
+
| `apex:actionSupport` | Standard DOM event handlers |
|
|
221
|
+
| `{!property}` merge fields | `{property}` template expressions |
|
|
222
|
+
|
|
223
|
+
### Embedding LWC in Visualforce (Lightning Out)
|
|
224
|
+
|
|
225
|
+
For incremental migration, embed LWC inside existing VF pages:
|
|
226
|
+
|
|
227
|
+
```html
|
|
228
|
+
<apex:includeLightning />
|
|
229
|
+
<div id="lwc-container"></div>
|
|
230
|
+
<script>
|
|
231
|
+
$Lightning.use("c:lwcOutApp", function() {
|
|
232
|
+
$Lightning.createComponent("c:accountDashboard",
|
|
233
|
+
{ recordId: "{!Account.Id}" }, "lwc-container");
|
|
234
|
+
});
|
|
235
|
+
</script>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Migration Checklist
|
|
239
|
+
|
|
240
|
+
1. **Inventory** — list all VF pages, controllers, usage frequency
|
|
241
|
+
2. **Categorize** — mark each page as Keep VF / Migrate / Retire
|
|
242
|
+
3. **Dependencies** — map controller extensions, custom components, static resources
|
|
243
|
+
4. **Security audit** — review XSS/injection issues before or during migration
|
|
244
|
+
5. **Feature parity** — replicate all VF functionality in LWC
|
|
245
|
+
6. **Test coverage** — write LWC Jest tests to match existing Apex test coverage
|
|
246
|
+
7. **Incremental rollout** — use Lightning Out to embed LWC in VF during transition
|
|
247
|
+
8. **Redirect** — update links, overrides, navigation to new LWC pages
|
|
248
|
+
9. **Deprecate** — mark old VF pages as inactive, remove after validation
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Related
|
|
253
|
+
|
|
254
|
+
### Guardrails
|
|
255
|
+
|
|
256
|
+
- **sf-security-constraints** — Enforced rules for XSS prevention, SOQL injection, CRUD/FLS enforcement
|
|
257
|
+
|
|
258
|
+
### Agents
|
|
259
|
+
|
|
260
|
+
- **sf-visualforce-reviewer** — For interactive, in-depth Visualforce review guidance
|