1. Overview
The Activity Panel is a Lightning Web Component (LWC) package developed by Cloud Maven, Inc. that provides a fully configurable, Salesforce-native activity timeline deployable on any standard or custom object record page.
Key capabilities:
-
Unified activity timeline showing Tasks, Events, Notes, and custom object records (Secure Email, SMS, eForms) in a single chronological view
-
Upcoming/Overdue section and Past Activities grouped by month
-
Inline creation of Tasks, Events, and Notes from action buttons
-
Screen Flow launch support for advanced actions such as Send Secure Email, Send SMS, Send eForm
-
Configurable filters: date range, assignee (all vs my activities), activity types, and sort order
-
Fully metadata-driven: buttons, field layouts, object configurations, and filter defaults are all stored in Custom Metadata with no code changes required
-
Schema-driven dynamic forms: modal field layouts rendered from metadata definitions at runtime
-
Real-time refresh via Salesforce Platform Events (Activity_UI_Event__e)
-
Responsive design with full-screen mobile panel and desktop modal variants
-
Visibility rules: buttons and dropdown items can be hidden per object or per record type
2. Component Architecture
2.1 Component Hierarchy
activityPanel (Root — Orchestrator)
│
├── activityRecordLogs (Timeline Renderer)
│ │
│ └── activityActionModal (Edit/Action Form)
│ │
│ └── recordLookup (Dynamic Search Lookup)
│
├── activityActionModal (New Activity Form — instantiated at root level)
│ │
│ └── recordLookup
│
└── lightning-flow (Screen Flow Host)
Rendered inside desktop modal or full-screen mobile panel
2.2 Communication Pattern
Parent ──────── @api properties ──────────► Child
(recordId, actionConfig,
filterConfig, schemaToUse)
Child ──────── Custom Events ─────────────► Parent
(closemodal: {refresh: true|false})
Parent ──────── @api method calls ─────────► activityRecordLogs
refreshActivityTimeline()
applyFilters()
expandActivityTimeline(expand)
toggleViewAllActivities(showAll)
EMP API ──────── Platform Event ─────────────► activityPanel.handlePlatformEvent()
(Activity_UI_Event__e)
└── triggers: refreshActivities() | hideFlowBox()
3. File and Folder Structure
force-app/main/default/
│
├── lwc/
│ ├── activityPanel/
│ │ ├── activityPanel.html Root template (toolbar, filters, modals)
│ │ ├── activityPanel.js Orchestrator: metadata load, routing, EMP
│ │ ├── activityPanel.css Layout, mobile panel, button styles
│ │ └── activityPanel.js-meta.xml Targets: AppPage, RecordPage, HomePage
│ │
│ ├── activityRecordLogs/
│ │ ├── activityRecordLogs.html Timeline template (open + past sections)
│ │ ├── activityRecordLogs.js Data loading, filtering, sorting, dedup
│ │ ├── activityRecordLogs.css Timeline item styles
│ │ └── activityRecordLogs.js-meta.xml
│ │
│ ├── activityActionModal/
│ │ ├── activityActionModal.html Dynamic form (desktop modal + mobile panel)
│ │ ├── activityActionModal.js Save/Delete logic, picklist loading
│ │ ├── activityActionModal.css Mobile styles
│ │ └── activityActionModal.js-meta.xml
│ │
│ └── recordLookup/
│ ├── recordLookup.html Search input + pill + dropdown
│ ├── recordLookup.js Search, debounce, entity toggle
│ ├── recordLookup.css
│ └── recordLookup.js-meta.xml
│
├── classes/
│ ├── ActivityPanelController.cls Metadata, object info, current record context
│ ├── ActivityRecordLogsController.cls Activity fetch, save, delete, schema
│ ├── ActivityWrapper.cls All data transfer objects (inner classes)
│ ├── ActivityUtils.cls Shared helpers, picklists, formatting
│ └── InvocableCreateActivityUIEvent.cls Invocable: publish Activity_UI_Event__e
│
└── customMetadata/
├── Activity_Panel_Configuration.Activity_Buttons_Configuration.md-meta.xml
├── Activity_Panel_Configuration.Default_Activity_Filters.md-meta.xml
├── Activity_Panel_Configuration.Default_Schema_Model.md-meta.xml
└── Activity_Panel_Configuration.Activity_Record_Configuration.md-meta.xml
4. Component: activityPanel
4.1 Purpose
The root and only exposed component. It is the single entry point placed on record pages. It owns all metadata loading, filter state, user context, EMP API subscription, button routing, and flow launching. It passes fully resolved configuration down to child components.
4.2 Public API
@api recordId String Salesforce record Id injected by the platform (record page context)
4.3 Key Internal State
State Property Type Description
─────────────────────────────────────────────────────────────────────
_allButtons Array All non-dropdown buttons from metadata
_allDropdownOptions Array All dropdown items from metadata
currentObjectApiName String API name of the current record object
currentRecordTypeDeveloperName String Developer name of current record type
schemaToUse Object Schema model for all modal forms
user Object Logged-in user (RelatedRecord shape)
who Object Pre-resolved Who reference for new activities
what Object Pre-resolved What reference for new activities
timeZone String User's IANA timezone string
filterConfig Object Active filter settings passed to child
actionConfig Object whatObjects, whoObjects, assignedToObjects, statusOptions
showModal Boolean Controls activityActionModal visibility
openFlow Boolean Controls lightning-flow visibility
isFilterBox Boolean Controls filter settings modal visibility
isMobile Boolean Drives desktop modal vs mobile panel rendering
selectedDateRange String Currently applied date range filter
selectedActivitiesToShow String All activities or My activities
selectedActivityTypes Array Currently applied activity type filter values
selectedSortOrder String Newest/Oldest dates first
pendingDateRange String Staging value while filter modal is open
pendingActivitiesToShow String Staging value while filter modal is open
pendingActivityTypes Array Staging value while filter modal is open
pendingSortOrder String Staging value while filter modal is open
4.4 Wire Adapters
Wire Call Apex Method Purpose
──────────────────────────────────────────────────────────────────────────────────
getWhatObjects ActivityPanelController Returns API names for What lookup objects
getObjects(who) ActivityPanelController Returns API names for Who lookup objects
getObjects(assignedTo) ActivityPanelController Returns API names for AssignedTo objects
getObjectInfos (what) lightning/uiObjectInfoApi Resolves icon, color, label for What objects
getObjectInfos (who) lightning/uiObjectInfoApi Resolves icon, color, label for Who objects
getObjectInfos (assignedTo) lightning/uiObjectInfoApi Resolves icon, color, label for AssignedTo objects
getUserAndCurrentRecord ActivityPanelController Logged-in user, Who/What pre-fill, object name
getPicklistValue(Task.Status) ActivityUtils Loads Task Status picklist into actionConfig
4.5 Computed Getters
Getter Returns
──────────────────────────────────────────────────────────────────────────────
buttons Visibility-filtered subset of _allButtons
dropdownOptions Visibility-filtered subset of _allDropdownOptions
noDropdownOptions Boolean — disables the More Actions menu when empty
showLabelsForButtons Boolean — shows button labels when componentWidth > 520px
useFlowModalView Boolean — true if NOT mobile (desktop gets modal, mobile gets panel)
expandAllLabel "Collapse All" or "Expand All" based on expand toggle state
viewAllLabel "View Less" or "View All" based on showAll toggle state
selectedActivityTypesString Human-readable string of active type filters for display in toolbar
4.6 Button Visibility Logic (_filterByVisibility)
For each button in _allButtons or _allDropdownOptions:
│
├── btn.hiddenFor is empty or null
│ └── SHOW the button (always visible)
│
└── btn.hiddenFor contains entries
│
├── No entry matches currentObjectApiName
│ └── SHOW the button
│
└── Entry matches currentObjectApiName
│
├── entry.recordTypes is empty
│ └── HIDE the button (hidden for all record types on this object)
│
└── entry.recordTypes is NOT empty
├── currentRecordTypeDeveloperName IS in the list → HIDE
└── currentRecordTypeDeveloperName NOT in the list → SHOW
5. Component: activityRecordLogs
5.1 Purpose
The timeline renderer. Receives data configuration from the parent and independently fetches, deduplicates, groups, sorts, filters, and renders all activity records. Exposes a public @api surface for parent-triggered operations.
5.2 Public API
@api Property / Method Direction Description
────────────────────────────────────────────────────────────────────────────────
recordId Input Record context from parent
schemaToUse Input Schema model for edit modals
actionConfig (getter/setter) Input whatObjects, whoObjects, assignedToObjects, statusOptions
filterConfig (getter/setter) Input Active filters; setter auto-triggers applyFilters()
loggedInUser (getter/setter) Input Logged-in user for "My activities" filtering
userTimeZone (getter/setter) Input User IANA timezone for date boundary calculations
selectedActivityTypesString Input Display string for active type filter (shown in modal header)
refreshActivityTimeline() Callable Full data reload from Apex
expandActivityTimeline(expand) Callable Expand or collapse all activity detail rows
toggleViewAllActivities(showAll) Callable Toggle between limited and full view of activities
applyFilters() Callable Re-apply all active filters on current data
5.3 Data Loading Workflow
connectedCallback()
│
└── loadActivityTimeline()
│
├── getActivityRecordConfigs() ← Apex: loads all ActivityRecordConfiguration objects
│
├── getSObjectTypeById(recordId) ← Apex: resolves object name from record Id
│
├── Promise.all([
│ For each config in activityRecordConfigs:
│ getCurrentAndParentActivities(recordId, config)
│ ])
│
├── Promise.all([
│ For each config in activityRecordConfigs:
│ fetchChildActivities(config)
│ │
│ └── For each childConfig entry in objectConfig[targetObject]:
│ getChildActivities(recordId, config,
│ child.sObjectName, child.relationField,
│ child.whereClause)
│ ])
│
├── DEDUPLICATION (global Id-based, per bucket)
│ upcomingAndOverdue: Set<id> seenUpcomingIds
│ pastActivities: Set<id> seenPastIds
│ Only unique recordId values are kept
│
└── processActivityTimeline(merged activityCollection)
5.4 Activity Processing Workflow
processActivityTimeline(activityCollection)
│
├── UPCOMING & OVERDUE
│ ├── sortUpcomingAndOverdue(activities)
│ │ Sort by sortingDate DESC, fallback createdDate ASC
│ │
│ ├── Map each activity:
│ │ Apply bgColor and iconColor as CSS custom properties
│ │ Mark isTask = true for Task subtype
│ │
│ ├── Set originalOpenActivities = section
│ └── showViewMoreOpenActivities = (count > 10)
│
├── PAST ACTIVITIES
│ ├── groupActivitiesByMonth(activities)
│ │ Group by M/YYYY key
│ │ Calculate sectionLabel: "Month YYYY" (e.g. "May 2026")
│ │ Calculate relativeLabel: "This Month", "Last Month", "N Months Ago", "N Years Ago"
│ │
│ ├── sortPastActivities(sections)
│ │ Sort each section's activities by sortingDate DESC
│ │ Sort sections by month DESC (latest month first)
│ │
│ ├── For each activity: apply bgColor, iconColor, menuDisabled
│ │ menuDisabled = true if sObjectName is NOT Task, Event, or ContentNote
│ │
│ ├── Set originalPastActivities = grouped sections
│ └── showViewMorePastActivities = (any section has > 10 activities)
│
└── applyFilters() ← run all active filters on the new data
5.5 Filter Application Workflow
applyFilters()
│
├── Step 1: filterByDateRange(filterConfig.dateRange)
│ │
│ ├── "All time"
│ │ filteredOpenActivities = copy of originalOpenActivities
│ │ filteredPastActivities = copy of originalPastActivities
│ │
│ ├── "Next 7 days"
│ │ startDate = today (user timezone) at 00:00:00
│ │ endDate = today + 7 days at 23:59:59
│ │ Filter open: activity.sortingDate <= gmtEndDate
│ │ Filter past: not included (range is future)
│ │
│ ├── "Last 7 days"
│ │ endDate = today (user timezone) at 23:59:59
│ │ startDate = today - 7 days at 00:00:00
│ │ Filter past: gmtStartDate <= sortingDate <= gmtEndDate
│ │
│ └── "Last 30 days"
│ endDate = today at 23:59:59
│ startDate = today - 30 days at 00:00:00
│ Filter past: gmtStartDate <= sortingDate <= gmtEndDate
│
├── Step 2: filterByAssignedTo(filterConfig.activitiesToShow)
│ │
│ ├── "All activities" → No filtering applied
│ │
│ └── "My activities"
│ Keep only activities where:
│ activity.assignedTo.recordId === loggedInUser.recordId
│ Applied to both open and past filtered sets
│
├── Step 3: filterByActivityTypes(filterConfig.activityTypes)
│ │
│ │ matchesType logic per activity:
│ │ ├── selected has "Task" AND sObjectName=Task AND subtype=Task → include
│ │ ├── selected has "Call" AND sObjectName=Task AND subtype=Call → include
│ │ ├── sObjectName=Task with any other subtype → exclude
│ │ └── selected has sObjectName (e.g. "Event", "ContentNote") → include
│ │
│ Applied to both open and past filtered sets
│
├── Step 4: Sort Order (filterConfig.sortOrder)
│ │
│ ├── "Newest dates first" → filteredOpenActivities sorted DESC (default)
│ └── "Oldest dates first" → filteredOpenActivities sorted ASC (reversed)
│
└── Step 5: updateVisibleList()
│
├── OPEN (Upcoming & Overdue)
│ count > 2:
│ "Newest" → show last 2 (.slice(-2))
│ "Oldest" → show first 2 (.slice(0, 2))
│ showViewMoreOpenActivities = true
│ count <= 2:
│ show all, showViewMoreOpenActivities = false
│
└── PAST
totalCount > 4:
show first 4 across sections (getFirstNActivities(4))
showViewMorePastActivities = true
totalCount <= 4:
show all, showViewMorePastActivities = false
5.6 Activity Type Checkbox Logic (in activityPanel)
handleActivityTypeChange(event)
│
│ Variables:
│ ALL = "All types"
│ INDIVIDUALS = all values except "All types"
│
├── User TICKED "All types" (was off, now on)
│ next = [ALL, ...INDIVIDUALS] ← select everything
│
├── User UNTICKED "All types" (was on, now off)
│ next = [] ← clear everything
│
└── "All types" state unchanged (user toggled an individual)
│
├── "All types" IS still selected
│ ├── All individuals still checked → next = [ALL, ...INDIVIDUALS]
│ ├── No individuals remain → next = []
│ └── Partial selection → next = selectedIndividuals (drop ALL)
│
└── "All types" NOT selected
├── No individuals selected → next = []
├── All individuals selected → next = [ALL, ...INDIVIDUALS] (auto-add ALL)
└── Partial selection → next = selectedIndividuals
5.7 Menu Action Routing Workflow
handleMenuClick(event)
│
├── Resolve activity from originalOpenActivities or originalPastActivities by recordId
│
├── action = "Edit"
│ └── Remap: Task → "Edit Task"
│ Event → "Edit Event"
│ ContentNote → "Edit Note"
│
└── buildModalData(action, activity)
│
├── "Create Follow-Up Task"
│ Return: { assignedTo, status:"Not Started", subject, who, what,
│ priority:"Normal", subtype:"Task" }
│
├── "Create Follow-Up Event"
│ Return: { assignedTo, subject, who, what,
│ startDate: next full hour (UTC),
│ endDate: startDate + 1 hour, allDayEvent: false }
│
├── "Delete"
│ Return: { recordId, sObjectName }
│
├── "Edit Task"
│ getTaskDetails(recordId, lstFields from schemaToUse['Edit Task'])
│ Map fieldAPIName → field.name on the returned record
│ Merge: who, what, assignedTo, recordId from activity
│
├── "Edit Event"
│ getEventDetails(recordId, lstFields from schemaToUse['Edit Event'])
│ Same mapping + merge pattern as Edit Task
│
└── "Edit Note"
getNoteDetails(recordId, lstFields from schemaToUse['Edit Note'])
Same mapping + merge pattern
6. Component: activityActionModal
6.1 Purpose
A schema-driven form component that handles all create, edit, delete, and status-change operations for Tasks, Events, and Notes. The layout of fields is entirely driven by the schemaToUse object keyed by action name. Renders as a desktop SLDS modal or a full-screen mobile panel.
6.2 Supported Actions
Action Key Object Operation
──────────────────────────────────────────────────────────────────────
"Log Call" Task Insert new Task (subtype=Call, status=Completed)
"New Task" Task Insert new Task (subtype=Task)
"New Event" Event Insert new Event
"New Note" ContentNote Insert new ContentNote + ContentDocumentLink
"Edit Task" Task Update existing Task
"Edit Event" Event Update existing Event
"Edit Note" ContentNote Update existing ContentNote
"Create Follow-Up Task" Task Insert follow-up Task from existing activity
"Create Follow-Up Event" Event Insert follow-up Event from existing activity
"Change Status" Task Update Task.Status only
"Change Priority" Task Update Task.Priority only
"Change Date" Task Update Task.ActivityDate only
"Edit Comments" Task Update Task.Description only
"ReOpen" Task Update Task.Status to a non-Completed value
"Delete" Any Delete record by Id
6.3 Dynamic Field Rendering
computedFields getter:
│
For each field in schemaToUse[action].fields:
│
├── Determine type variant:
│ isInput → lightning-input (text, date, datetime)
│ isCheckbox → lightning-input type=checkbox
│ isRecordLookup → c-record-lookup (custom lookup)
│ isCombobox → lightning-combobox (standard picklist)
│ isCustomCombobox → lightning-input + dropdown overlay (subject field)
│ isTextArea → lightning-textarea
│ isRichText → lightning-input-rich-text
│
├── For startDate/endDate fields:
│ allDayEvent=true → type="date", labels "Start Date"/"End Date"
│ allDayEvent=false → type="datetime", labels "Start"/"End"
│
├── For lookup fields (who, what, assignedTo):
│ objects = getObjects(fieldName)
│ who → actionConfig.whoObjects
│ what → actionConfig.whatObjects
│ assignedTo → actionConfig.assignedToObjects
│
├── For combobox fields:
│ getOptions(fieldName, fieldAPIName, options, controllingField)
│ Static options defined in schema → use as-is
│ controllingField defined → use dependentMap[currentControllingValue]
│ Otherwise → use optionsMap[fieldAPIName]
│
└── required flag:
Controlling field present + no options loaded → required=false (auto-disabled)
field.name='who' and activity.who.recordId exists → required=true
field.name='what' and activity.what.recordId exists → required=true
Otherwise: field.required from schema
6.4 Picklist Loading Workflow
loadPicklistOptions() — called on connectedCallback
│
├── Extract all fields with isCombobox=true or isCustomCombobox=true
│ from schemaToUse[action].fields
│
└── For each such field:
│
├── Has controllingField defined?
│ YES:
│ getDependentPicklistValues(
│ sObjectName: "Event" or "Task",
│ controllingFieldAPIName,
│ dependentFieldAPIName
│ )
│ Store result in optionsMap[fieldAPIName]
│ as Map<controllingValue, List<{label,value}>>
│
└── NO (simple picklist):
getPicklistValue(sObjectName, fieldAPIName)
Store result in optionsMap[fieldAPIName]
as List<{label,value}>
6.5 Save Workflow
handleSave()
│
├── isValid(): check all lightning-input, lightning-combobox, c-record-lookup
│ reportValidity on each; return false if any invalid
│
├── isReOpen? → handleReOpenTask()
│ Build: { Id, Status }
│ saveTask(JSON)
│ closeModal(refresh=true)
│
├── isEvent? → handleSaveEvent()
│ Build field map from schemaToUse[action].fields:
│ startDate/endDate + allDayEvent=true → toDateOnly() (strip time)
│ who/what/assignedTo → extract .recordId
│ all other fields → direct value
│ Include Id if editing (recordId != null)
│ saveEvent(JSON)
│ closeModal(refresh=true)
│
├── isNote? → handleSaveNote()
│ Build field map from schemaToUse[action].fields
│ saveNote(JSON, relatedToId=formData.relatedTo.recordId)
│ On insert: Apex creates ContentDocumentLink automatically
│ closeModal(refresh=true)
│
└── Task → handleSaveTask()
Build field map from schemaToUse[action].fields:
startDate/endDate → toDateOnly()
who/what/assignedTo → extract .recordId
all other fields → direct value
Include Id if editing
Include TaskSubtype if creating new (subtype != null AND no Id)
saveTask(JSON)
closeModal(refresh=true)
6.6 Date Adjustment Logic
adjustDateTimes(changedFieldName)
│
Trigger: whenever startDate or endDate changes
│
├── If end <= start:
│ allDayEvent=true:
│ startDate changed → endDate = startDate
│ endDate changed → startDate = endDate
│
│ allDayEvent=false:
│ startDate changed → endDate = startDate + 1 hour
│ endDate changed → startDate = endDate - 1 hour
│
└── If end > start: no adjustment needed
6.7 Dependent Field Clear Logic
clearDependentFields(changedFieldName)
│
├── Find all fields in schemaToUse[action].fields
│ where field.controllingField === changedFieldName
│
└── For each dependent field:
Set formData[field.name] = ''
(triggers re-render and clears the dependent combobox)
7. Component: recordLookup
7.1 Purpose
A reusable custom lookup field that supports multi-object search. Used inside activityActionModal for the Who, What, and Assigned To fields. Renders a search input with debounced SOSL-backed search, a results dropdown, and a pill to display the selected record.
7.2 Public API
@api Property Type Description
────────────────────────────────────────────────────────────────────
objects Array List of {apiName, label, iconUrl, color} objects to search across
defaultEntity String API name of the default selected object type
fieldLabel String Label displayed above the input
fieldValue Object Pre-selected value: {recordId, name, sObjectType}
required Boolean Whether the field must have a selection
messageWhenValueMissing String Custom validation error message
@api Method Returns Description
────────────────────────────────────────────────────────────────────
reportValidity() Boolean Sets error state and returns false if required and empty
checkValidity() Boolean Returns false if required and empty (no side effects)
7.3 Search Workflow
User types in search input
│
├── searchTerm.length < 2 → clear results, cancel debounce
│
└── searchTerm.length >= 2
│
└── debounce(fetchRecords, 250ms)
│
└── getSearchedRecords(sObjectName, searchTerm)
│
├── Apex checks Record_Lookup_Configuration for display field
│ (defaults to "Name" if not configured)
│
└── SOQL: SELECT Id, <displayField>
FROM <sObjectName>
WHERE <displayField> LIKE '%term%'
LIMIT 20
7.4 Record Selection Workflow
User clicks a result item
│
├── selectedRecord = { Id, Name }
├── showPill = true
└── Dispatch 'change' event:
{ detail: { value: {
recordId: selectedRecord.Id,
name: selectedRecord.Name,
sObjectType: selectedEntity.apiName
} } }
User clicks the pill X (clear)
│
├── selectedRecord = {}
├── showPill = false
└── Dispatch 'change' event:
{ detail: { value: { recordId: null, name: null, sObjectType: null } } }
8. Apex Backend Layer
8.1 ActivityPanelController
Handles metadata retrieval, object API name resolution for lookups, and current record context resolution.
Method Access Description
──────────────────────────────────────────────────────────────────────────────────────────
getMetadataValue(devName) @AuraEnabled Reads named entry from mapActivityConfigurations
Returns JSON: {isSuccess, <devName>: value}
or {isSuccess:false, errorMessage: ...}
getObjects(fieldName) @AuraEnabled Reflects Task.WhoId or Task.OwnerId field
cacheable=true Returns List<String> of referenced object API names
getWhatObjects(devName) @AuraEnabled Reads Related_To_Objects from Activity_Record_Configuration
cacheable=true Returns List<String> (e.g. ["Account","Opportunity","Case"])
getUserAndCurrentRecord @AuraEnabled Resolves logged-in user, timezone, record name,
(recordId) cacheable=true record type, and pre-fills Who/What from
Record_Lookup_Configuration in metadata
Returns ActivityWrapper
8.2 getUserAndCurrentRecord Resolution Logic
getUserAndCurrentRecord(recordId)
│
├── result.loggedInUser = {userId, userName, "User"}
├── result.userTimeZone = UserInfo.getTimeZone().getID()
│
├── Resolve objectName from recordId (getSObjectType)
├── result.objectApiName = objectName
│
├── Load Record_Lookup_Configuration from metadata for objectName
│ Extract: whoIdField, whoIdDisplayName, whatIdField, whatIdDisplayName
│
├── Build dynamic SOQL:
│ SELECT Name, RecordTypeId, RecordType.DeveloperName,
│ <whoIdField>, <whoIdDisplayName>,
│ <whatIdField>, <whatIdDisplayName>
│ FROM <objectName> WHERE Id = :recordId
│
├── result.recordTypeDeveloperName = from RecordType.DeveloperName
│
├── Resolve WHO:
│ whoIdField defined AND field has a value on record
│ → result.who = {whoId, whoDisplayName, whoObjectType}
│ whoIdField defined but empty AND objectName IN whoSet
│ → result.who = currentRecord (the record itself is the who)
│ whoIdField NOT defined AND objectName IN whoSet
│ → result.who = currentRecord
│
└── Resolve WHAT:
Same pattern as WHO but against whatSet and whatIdField
whatSet = Related_To_Objects from metadata (Account, Opportunity, Case, etc.)
8.3 ActivityRecordLogsController
The core data layer. Fetches all activity records, handles saves, deletes, and returns schema.
Method Access Description
──────────────────────────────────────────────────────────────────────────────────────────────────
getSchema(recordId) @AuraEnabled Resolves schema model:
1. Check object has active record types
2. If no → query by objectName alone
3. If yes → query by objectName::RecordTypeName
or objectName::DeveloperName
4. Fallback → Default Schema Model
Returns JSON string of schema map
getActivityRecordConfigs() @AuraEnabled Deserializes Activity_Record_Configuration
Returns List<ActivityRecordConfiguration>
getCurrentAndParentActivities @AuraEnabled Dispatches by sObjectName:
(recordId, configJson) Task → getTasks()
Event → getEvents()
ContentNote → getNotes()
else → getCustomRecords()
Returns ActivityWrapper.ActivityCollection
getChildActivities @AuraEnabled Dispatches by sObjectName:
(recordId, configJson, Task → getChildTasks()
childObjectName, Event → getChildEvents()
relationField, whereClause) ContentNote → getChildNotes()
else → getChildCustomObjects()
Returns ActivityWrapper.ActivityCollection
getTaskDetails(recordId, lstFields) @AuraEnabled Dynamic SOQL on Task, returns JSON
getEventDetails(recordId, lstFields) @AuraEnabled Dynamic SOQL on Event, returns JSON
getNoteDetails(recordId, lstFields) @AuraEnabled Dynamic SOQL on ContentNote, returns JSON
saveTask(sActivity) @AuraEnabled Upsert Task: insert if no Id, update if Id present
Uses field describe to convert types correctly
saveEvent(sActivity) @AuraEnabled Upsert Event: same pattern
saveNote(sActivity, relatedToId) @AuraEnabled Upsert ContentNote
On insert: also inserts ContentDocumentLink
(ShareType=V, Visibility=AllUsers)
getSearchedRecords(sObjectName, @AuraEnabled SOQL LIKE search, respects
searchTerm) Record_Lookup_Configuration display fields
LIMIT 20
deleteActivity(recordId) @AuraEnabled Database.delete(recordId)
8.4 Parent Activity Fetch Logic (getTasks / getEvents / getNotes)
getTasks(recordId, lstParentFields, activityConfig)
│
├── Resolve targetObjectType from recordId
│
├── SOQL: SELECT Id, <lstParentFields> FROM <targetObject> WHERE Id = :recordId
│
├── Build setWhoId / setWhatId:
│ targetObject in {Contact, Lead} → setWhoId.add(recordId)
│ otherwise → setWhatId.add(recordId)
│
├── For each parentField value on the record:
│ Resolve type: Contact/Lead → setWhoId, else → setWhatId
│
├── Build WHERE clause:
│ (WhoId IN :setWhoId OR WhatId IN :setWhatId)
│ combined with activityConfig.whereClause
│
├── Query Tasks with activityConfig.fields
│
└── For each Task:
├── createActivityFromTask(oTask) → ActivityRecord
├── buildSubject(oTask, activityConfig.subject)
│
├── status == 'Completed' OR completedDateTime != null
│ sortingDate = completedDateTime
│ → pastActivities
│
└── Not completed:
isOverdue = (activityDate < today)
sortingDate = DateTime from activityDate
→ upcomingAndOverdue
8.5 Child Activity Fetch Logic (getChildTasks / getChildEvents / getChildNotes)
getChildTasks(childObjectName, relationField, recordId, childWhereClause, activityConfig)
│
├── SOQL: SELECT Id FROM <childObjectName>
│ WHERE <relationField> = :recordId [AND <childWhereClause>]
│
├── Collect Ids into setWhoId (Contact/Lead) or setWhatId (others)
│
├── Query Tasks using same WHERE pattern as parent fetch
│
└── Same classification into pastActivities / upcomingAndOverdue
getChildNotes(childObjectName, relationField, recordId, childWhereClause, activityConfig)
│
├── SOQL: SELECT Id FROM <childObjectName>
│ WHERE <relationField> = :recordId [AND <childWhereClause>]
│
├── Collect all Ids into setParentRecordId
│
├── [SELECT ContentDocumentId FROM ContentDocumentLink
│ WHERE LinkedEntityId IN :setParentRecordId]
│
├── Collect docIds
│
└── Query ContentNotes WHERE Id IN :docIds
→ all notes go into pastActivities
8.6 ActivityUtils — Shared Utilities
Method Description
──────────────────────────────────────────────────────────────────────────────────────
createRelatedRecord(id, name, type) Builds ActivityWrapper.RelatedRecord
getSObjectTypeById(recordId) Returns object API name from Id prefix
formatDateTime(sortingDate, showTime, Returns human-readable date string:
defaultValue, isAllDayEvent) "Today", "Tomorrow", "Yesterday", "MMM d"
If showTime: prepends "hh:mm a | " or "All-Day Event | "
getPicklistValue(sObjectName, fieldName) Returns serialized List<{label,value}> for a picklist field
getDependentPicklistValues(sObjectName, Decodes Salesforce base64 validFor bitmask
controllingFieldName, dependentFieldName) Returns Map<controllingValue, List<{label,value}>>
buildTaskEventSummary(activity, isPast, Builds inline summary tokens:
recordId) "You / <Name> logged a call / had a task / had an event
with <Who> about <What>"
buildDetails(oRec, lstTemplateItem) Processes template items recursively:
type=text, record, owner, conditionalText,
conditionalGroup, link
buildSubject(oRec, subjectTemplate) Resolves subject from field value,
staticValue, or conditionalText mapping
getFieldValueAsString(record, fieldPath) Traverses dot-notation field paths on SObjects
(e.g. "RecordType.DeveloperName")
mapActivityConfigurations Lazy-loaded static property:
Map<DeveloperName, Value__c>
from Activity_Panel_Configuration__mdt
8.7 InvocableCreateActivityUIEvent
Purpose: Allows Salesforce Flows to trigger real-time UI refresh on the Activity Panel
Invocable Input:
parentRecordId (required) The Id of the record page hosting the Activity Panel
eventType (required) The action to trigger:
"Close And Refresh" → close open flow + refresh timeline
"Refresh" → refresh timeline only
Behavior:
Publishes Activity_UI_Event__e platform events via EventBus.publish()
Usage in Flows:
Add "Create Activity UI Event" action element
Pass the record Id as parentRecordId
Pass "Close And Refresh" or "Refresh" as eventType
9. Custom Metadata Configuration
All configuration lives in the custom metadata type: Activity_Panel_Configuration__mdt (namespace: cm_activity)
9.1 Activity_Buttons_Configuration
Controls which buttons and dropdown items appear in the action toolbar.
JSON Structure:
{
"buttonMapping": [
{
"name": "log_a_call", Internal key used in switch/case routing
"label": "Log a Call", Display label and modal heading
"iconName": "standard:log_a_call", SLDS icon name
"class": "slds-m-right_xx-small", CSS class on the icon
"modalClass": "", SLDS modal size class (empty = default)
"order": 1, Sort order for non-dropdown buttons
"isDropdown": false, false = toolbar button, true = More Actions menu
"screenFlowName": null, If set, opens a Flow instead of modal
"parameterName": null, Flow input variable name for the record Id
"hiddenFor": [ Optional visibility rules
{
"objectName": "Case",
"recordTypes": ["SupportCase"] Empty = hide for all record types on this object
}
]
}
]
}
Button Names with Hardcoded Routing:
"log_a_call" → Opens Log Call modal (Task, subtype=Call, status=Completed)
"task" → Opens New Task modal (Task, subtype=Task, status=Not Started)
"event" → Opens New Event modal (Event, start=next hour, end=start+1hr)
"note" → Opens New Note modal (ContentNote)
Button Names with screenFlowName (open Flow):
"secure_email" → Flow: Send_Secure_Email
"secure_sms" → Flow: Send_SMS
"send_eform" → Flow: Send_eForm
9.2 Default_Activity_Filters
Defines available options and default selected values for the filter settings modal.
JSON Structure:
{
"options": {
"activityTypeOptions": [
{ "label": "All types", "value": "All types" },
{ "label": "Events", "value": "Event" },
{ "label": "Logged calls", "value": "Call" },
{ "label": "Tasks", "value": "Task" },
{ "label": "Notes", "value": "ContentNote" },
{ "label": "Secure Email", "value": "cmsecureemail__Secure_EmailMessage__c" },
{ "label": "SMS", "value": "smsefax_guru__SMS_Message__c" },
{ "label": "eForms", "value": "docgen_esign__Document_Workflow__c" }
],
"dateRangeOptions": ["All time", "Next 7 days", "Last 7 days", "Last 30 days"],
"activitiesToShowOptions":["All activities", "My activities"],
"sortOrderOptions": ["Oldest dates first", "Newest dates first"]
},
"selectedValues": {
"selectedDateRange": "All time",
"selectedActivitiesToShow": "All activities",
"selectedActivityTypes": ["All types"],
"selectedSortOrder": "Newest dates first"
}
}
9.3 Default_Schema_Model
Defines the field layout for every modal action. The activityPanel loads this on init via getSchema() and uses it as schemaToUse throughout.
JSON Structure:
{
"<Action Key>": {
"label": "Display label for modal header",
"modalClass": "slds-modal slds-fade-in-open slds-modal_small",
"action": "internal action identifier",
"fields": [
{
"label": "Field Label",
"name": "formData property key",
"fieldAPIName": "Salesforce field API name for save",
"class": "slds-col slds-size_1-of-2",
"isInput": true, text, date, datetime inputs
"isCheckbox": true, boolean checkbox
"isRecordLookup": true, c-record-lookup component
"isCombobox": true, lightning-combobox (picklist)
"isCustomCombobox": true, text input with dropdown overlay (subject field)
"isTextArea": true, lightning-textarea
"isRichText": true, lightning-input-rich-text
"type": "date", for isInput fields: text|date|datetime
"required": true,
"defaultEntity": "Contact", default object for isRecordLookup
"options": ["Call"], static options for isCustomCombobox
"controllingField": "status", formData key of controlling field
"controllingFieldAPIName": "Status" Salesforce API name of controlling field
}
]
}
}
Defined Action Keys:
"Log Call", "New Task", "New Event", "New Note"
"Edit Task", "Edit Event", "Edit Note"
"Create Follow-Up Task", "Create Follow-Up Event"
"Change Status", "Change Priority", "Change Date", "Edit Comments"
"ReOpen", "Delete"
9.4 Activity_Record_Configuration
Drives the data fetching layer. Defines what to query, how to display it, and which child/parent relationships to traverse.
JSON Structure:
{
"Activity_Record_Configuration": [
{
"sObjectName": "Task",
"fields": "Id, Subject, ActivityDate, ...", SOQL field list
"whereClause": "WHERE TaskSubtype IN :ALLOWED_TASK_SUBTYPES AND ...",
"subject": { "type": "text", "fieldName": "Subject" },
"summaryTemplate": [], Inline summary tokens
"detailTemplate": [ ... ], Expanded detail tokens
"icon": null, Falls back to createActivityFromTask defaults
"bgColor": null,
"objectConfig": {
"Account": {
"parentFields": [],
"childConfig": [
{ "sObjectName": "Contact", "relationField": "AccountId", "whereClause": "" },
{ "sObjectName": "Opportunity", "relationField": "AccountId", "whereClause": "" }
]
}
}
}
],
"Related_To_Objects": "Account,Opportunity,Case",
"Record_Lookup_Configuration": {
"Contact": {
"whoIdField": "Id",
"whoIdDisplayName": "Name",
"whatIdField": "AccountId",
"whatIdDisplayName": "Account.Name"
},
"Case": {
"whatIdField": "Id",
"whatIdDisplayName": "CaseNumber"
}
}
}
Template Item Types (summaryTemplate and detailTemplate):
"text" → Static text, field value, or conditional on dependsOn
"record" → Clickable link: value from labelField, Id from fieldName
"owner" → Shows "You" if logged-in user is owner, else owner name + link
"conditionalText" → Value is looked up from a map by dependsOn field value
"conditionalGroup"→ Renders a nested list of template items based on dependsOn value
"link" → Value from field, href template with {} placeholder substitution
10. Key Workflows
10.1 Component Initialization Workflow
activityPanel.connectedCallback()
│
├── loadMetadata()
│ │
│ ├── getMetadataValue("Activity_Buttons_Configuration")
│ │ ├── Parse buttonMapping array
│ │ ├── _allButtons = buttons where isDropdown=false, sorted by order
│ │ └── _allDropdownOptions = buttons where isDropdown=true
│ │
│ └── getMetadataValue("Default_Activity_Filters")
│ ├── Populate dateRangeOptions, activitiesToShowOptions,
│ │ activityTypeOptions, sortOrderOptions
│ ├── Set selectedDateRange, selectedActivitiesToShow,
│ │ selectedSortOrder from selectedValues
│ ├── Resolve selectedActivityTypes:
│ │ "All types" in stored → select ALL including individuals
│ │ All individuals present → auto-add "All types"
│ │ Otherwise → keep stored subset
│ ├── Copy selected to pending (initializes filter modal state)
│ └── filterConfig = getFilters() → passed to activityRecordLogs
│
├── getSchema(recordId)
│ └── Resolves schema model → this.schemaToUse
│ (object+recordType specific, or Default Schema Model fallback)
│
├── onError(handler) → EMP API global error handler
│
├── subscribeToEvent()
│ └── subscribe("/event/Activity_UI_Event__e", replayId=-1, handlePlatformEvent)
│
└── isMobile = (window.innerWidth <= 600)
window.addEventListener("resize", handleResize)
Wire adapters fire in parallel (reactive):
getWhatObjects → apiNames.whatObjectsApiNames
getObjects(who) → apiNames.whoObjectsApiNames
getObjects(assignedTo) → apiNames.assignedToObjectsApiNames
getObjectInfos (what) → actionConfig.whatObjects (with icons/colors)
getObjectInfos (who) → actionConfig.whoObjects
getObjectInfos (assignedTo) → actionConfig.assignedToObjects
getUserAndCurrentRecord → user, who, what, timeZone, currentObjectApiName, recordTypeDeveloperName
getPicklistValue(Task.Status) → actionConfig.statusOptions
10.2 New Activity Creation Workflow (Toolbar Button Click)
User clicks a toolbar button (e.g. "Log a Call")
│
├── handleClick(event)
│ │
│ ├── Find button config by data-name attribute
│ │
│ ├── btn.screenFlowName defined?
│ │ YES:
│ │ openFlow = true
│ │ modalHeading = btn.label
│ │ modalClass = btn.modalClass
│ │ setTimeout 1000ms → querySelector('.screenFlow')
│ │ .startFlow(btn.screenFlowName, [
│ │ { name: btn.parameterName, type: 'String', value: recordId }
│ │ ])
│ │ STOP (no modal)
│ │
│ └── btn.screenFlowName NOT defined → switch on name:
│
├── case "log_a_call":
│ activity = {
│ subtype: 'Call', status: 'Completed', priority: 'Normal',
│ what: { ...this.what }, who: { ...this.who }, assignedTo: { ...this.user }
│ subject: 'Call'
│ }
│ action = 'Log Call'
│
├── case "task":
│ activity = {
│ subtype: 'Task', status: 'Not Started', priority: 'Normal',
│ what: { ...this.what }, who: { ...this.who }, assignedTo: { ...this.user }
│ }
│ action = 'New Task'
│
├── case "event":
│ startDate = next full hour UTC
│ endDate = startDate + 1 hour UTC
│ activity = {
│ what: {...}, who: {...}, assignedTo: {...},
│ startDate, endDate, allDayEvent: false
│ }
│ action = 'New Event'
│
└── case "note":
activity = { relatedTo: { ...this.what, ...this.who } }
action = 'New Note'
└── showModal = true
modalHeading = btn.label
→ renders c-activity-action-modal
10.3 Filter Settings Workflow
User clicks the Settings icon
│
└── openFilterBox()
Seed pending state from current applied values:
pendingDateRange = selectedDateRange
pendingActivitiesToShow = selectedActivitiesToShow
pendingActivityTypes = [...selectedActivityTypes]
pendingSortOrder = selectedSortOrder
isFilterBox = true
modalHeading = 'Filter Settings'
User makes changes in the modal
│
├── handleDateRangeChange → pendingDateRange = event.detail.value
├── handleActivitiesToShowChange → pendingActivitiesToShow = event.detail.value
├── handleSortOrderChange → pendingSortOrder = event.detail.value
└── handleActivityTypeChange → complex "All types" toggle logic → pendingActivityTypes
User clicks Apply
│
└── handleApply()
│
├── isValid():
│ pendingActivityTypes.length === 0
│ → setCustomValidity("Please select at least one activity type.")
│ → return false
│
├── Commit pending → selected:
│ selectedActivityTypes = [...pendingActivityTypes]
│ selectedSortOrder = pendingSortOrder
│ selectedDateRange = pendingDateRange
│ selectedActivitiesToShow = pendingActivitiesToShow
│
├── isFilterBox = false
│
└── activityTimeline.applyFilters()
filterConfig = getFilters()
(strips "All types" sentinel before passing to backend)
10.4 Platform Event Refresh Workflow
Flow publishes Activity_UI_Event__e
(via InvocableCreateActivityUIEvent or direct Apex)
│
└── EMP API delivers to subscribed activityPanel
│
└── handlePlatformEvent(message)
│
├── Extract payload.cm_activity__Parent_Record_Id__c
├── Extract payload.cm_activity__Event_Type__c
│
├── parentId !== this.recordId → IGNORE (wrong record page)
│
└── switch on eventType:
│
├── "Close And Refresh"
│ setTimeout 2000ms:
│ openFlow = false
│ refreshActivities()
│ └── activityTimeline.refreshActivityTimeline()
│
└── "Refresh"
refreshActivities()
└── activityTimeline.refreshActivityTimeline()
10.5 Expand All / View All Workflow
Expand All button clicked
│
└── expandAll()
this.expand = !this.expand
activityTimeline.expandActivityTimeline(this.expand)
│
├── Map all upcomingAndOverdue.activities:
│ isOpen = expand
│ showMoreIcon = expand ? chevrondown : chevronright
│
└── Map all pastActivities sections → each activity:
isOpen = expand
showMoreIcon = expand ? chevrondown : chevronright
View All button clicked
│
└── viewAll()
this.showAll = !this.showAll
activityTimeline.toggleViewAllActivities(this.showAll)
│
├── showAll = true:
│ upcomingAndOverdue = full originalOpenActivities
│ pastActivities = full originalPastActivities
│ showViewMoreOpenActivities = false
│ showViewMorePastActivities = false
│
└── showAll = false:
upcomingAndOverdue = last 2 activities (.slice(-2))
pastActivities = first 4 activities (getFirstNActivities(4))
showViewMoreOpenActivities = (total open > 2)
showViewMorePastActivities = (total past > 4)
10.6 Task Completion Workflow
User checks the checkbox on an open activity (Task only)
│
└── handleCompletion(event)
│
├── isCompleted = true:
│ activity.status = 'Completed'
│ activity.subjectClass = 'linethrough' (strike-through visual)
│ saveTask({ Status: 'Completed', Id: activity.recordId })
│
└── isCompleted = false (unchecking):
openModal('ReOpen', activity)
→ activityActionModal opens with ReOpen action
→ User selects new status from radio group
→ handleSave → handleReOpenTask → saveTask({ Status, Id })
→ closeModal(refresh=true)
→ loadActivityTimeline() full reload
10.7 Schema Resolution Workflow
getSchema(recordId) [Apex - ActivityRecordLogsController]
│
├── Resolve objectName from recordId
│
├── Describe object for active record types
│
├── No active record types:
│ Query: WHERE Object_Name__c = :objectName
│ Found → return that schema
│ Not found → useDefault = true
│
└── Has active record types:
Query record: SELECT Id, RecordTypeId FROM <objectName> WHERE Id = :recordId
Resolve RecordTypeName and DeveloperName
Build lookup set:
recName1 = objectName + "::" + RecordTypeName
recName2 = objectName + "::" + DeveloperName
recName3 = objectName
Query: WHERE Object_Name__c IN (:recName1, :recName2, :recName3)
Found → return first match
Not found → useDefault = true
useDefault = true:
Query: WHERE Object_Name__c = 'Default Schema Model'
Returns the global default schema for all actions
11. Platform Event Integration
11.1 Event Object
Platform Event API Name: Activity_UI_Event__e (namespace: cm_activity)
Fields:
cm_activity__Parent_Record_Id__c String 18-char Id of the parent record
cm_activity__Event_Type__c String "Close And Refresh" | "Refresh"
11.2 Subscription Lifecycle
connectedCallback → subscribe(channelName, replayId=-1, handler)
Stores subscription object in this.subscription
disconnectedCallback → unsubscribe(this.subscription)
Cleans up channel listener when component is removed
11.3 Publishing from a Flow (via Invocable)
Invocable Method: InvocableCreateActivityUIEvent.createActivityUIEvent(lstWrappers)
Input Variables:
parentRecordId (required) Id of the record page
eventType (required) "Close And Refresh" or "Refresh"
Flow Usage:
1. In Flow Builder, add an Action element
2. Search for "Create Activity UI Event"
3. Set parentRecordId = {!recordId} (or any record Id variable)
4. Set eventType = "Close And Refresh"
This will close the open flow panel on the Activity Panel and refresh the timeline
12. Mobile Responsiveness
12.1 Breakpoint
MOBILE_BREAKPOINT = 600px (defined as constant in both activityPanel.js and activityActionModal.js)
isMobile is set on:
connectedCallback() window.innerWidth <= 600
window.resize event handler updated dynamically
12.2 Responsive Behavior Matrix
Component / Element Desktop (> 600px) Mobile (<= 600px)
─────────────────────────────────────────────────────────────────────────────────────────
Button labels Shown (componentWidth > 520) Hidden (icon only)
Screen Flow container SLDS modal (slds-modal_medium) Full-screen panel (mobile-panel)
activityActionModal SLDS modal Full-screen panel
Exception: Delete action SLDS modal SLDS modal (always modal on mobile)
Filter settings modal SLDS modal SLDS modal (unchanged)
Mobile panel structure N/A fixed: top/left/right/bottom=0
flex-column with sticky header/footer
scrollable content area
-webkit-overflow-scrolling: touch
overscroll-behavior: contain
12.3 Mobile Panel Layout
.mobile-panel (position:fixed, full viewport, z-index:9999, flex-column)
│
├── .mobile-header (flex:0 0 auto, border-bottom)
│ ├── Back button (absolute left)
│ └── Title (centered, truncated)
│
├── .mobile-content (flex:1 1 auto, overflow-y:auto, touch-scroll)
│ └── Form fields / lightning-flow content
│
└── .mobile-footer (flex:0 0 auto, border-top, justify-content:flex-end)
└── Cancel / Save buttons
13. Data Models and Structures
13.1 ActivityWrapper (Apex)
ActivityWrapper
├── loggedInUser RelatedRecord
├── who RelatedRecord
├── what RelatedRecord
├── userTimeZone String
├── objectApiName String
└── recordTypeDeveloperName String
ActivityWrapper.RelatedRecord
├── recordId String
├── name String
└── sObjectType String
ActivityWrapper.ActivityCollection
├── upcomingAndOverdue List<ActivityRecord>
└── pastActivities List<ActivityRecord>
ActivityWrapper.ActivityRecord
├── recordId String
├── sObjectName String
├── subtype String (Task subtype: "Task" | "Call")
├── subject String
├── status String
├── description String
├── activityDate Date
├── createdDate DateTime
├── completedDateTime DateTime
├── isOverdue Boolean
├── priority String
├── isHighPriority Boolean
├── location String (Event only)
├── startDate DateTime (Event only)
├── endDate DateTime (Event only)
├── allDayEvent Boolean (Event only)
├── bgColor String hex color for timeline left border
├── icon String SLDS icon name
├── iconColor String hex color for icon
├── showMoreIcon String chevronright or chevrondown
├── isOpen Boolean expanded detail state
├── sortingDate DateTime used for sorting and date display
├── dateLabel String "Today", "Tomorrow", "Mar 5", etc.
├── dateClass String SLDS class for date display color
├── menuButtons List<String> context menu item labels
├── lstSummary List<ActivityDetail> inline summary tokens
├── lstExpandedDetail List<ActivityDetail> expanded detail tokens
├── assignedTo RelatedRecord
├── who RelatedRecord
└── what RelatedRecord
ActivityWrapper.ActivityDetail
├── label String field label for expanded view
├── value String display text
├── href String optional external link URL
├── recordId String optional: makes value a clickable Salesforce nav link
├── cssClass String SLDS grid size class
└── scrollable Boolean if true, renders in a scrollable container (max-height: 90px)
13.2 ActivityRecordConfiguration (Apex Inner Class)
ActivityRecordConfiguration
├── sObjectName String Object to query (Task, Event, ContentNote, custom)
├── whereClause String SQL WHERE clause fragment appended to base query
├── fields String Comma-separated field list for SELECT
├── subject TemplateItem How to derive the activity subject
├── summaryTemplate List<TemplateItem> Inline summary row definition
├── detailTemplate List<TemplateItem> Expanded detail row definition
├── icon String SLDS icon name
├── iconColor String hex color
├── bgColor String hex color for timeline bar
└── objectConfig Map<String, ObjectConfig> Per-object parent/child config
ObjectConfig
├── parentFields List<String> Lookup field API names on the current object whose
│ values should also be included in the activity query
└── childConfig List<ChildConfig>
ChildConfig
├── sObjectName String Child object to traverse (e.g. "Contact")
├── relationField String Lookup field on child pointing to current record (e.g. "AccountId")
└── whereClause String Optional additional filter on child object query
TemplateItem
├── type String "text" | "record" | "owner" | "link" |
│ "conditionalText" | "conditionalGroup"
├── fieldName String API name of the value field
├── labelField String API name of the display name field (for record type)
├── dependsOn String Field whose value controls conditional output
├── staticValue String Literal text value
├── conditionalTextConditions Map<String,String> value map by dependsOn value
├── conditionalGroupConditions Map<String,List<TemplateItem>> nested templates by dependsOn value
├── defaultValue String Fallback for conditionalText when no match
├── label String Field label in expanded detail view
├── href String URL template with {} as value placeholder
├── cssClass String SLDS grid class override
├── scrollable Boolean Enable scrollable container
└── recordTypesForStatic List<String> For owner type: treat as "You" for these record types
13.3 filterConfig Object (LWC)
filterConfig passed from activityPanel to activityRecordLogs:
{
dateRange: "All time" | "Next 7 days" | "Last 7 days" | "Last 30 days",
activitiesToShow: "All activities" | "My activities",
activityTypes: ["Event", "Task", "Call", "ContentNote", ...],
Note: "All types" sentinel is stripped before building this object
sortOrder: "Newest dates first" | "Oldest dates first"
}
13.4 actionConfig Object (LWC)
actionConfig built in activityPanel and passed to activityRecordLogs and activityActionModal:
{
whatObjects: [
{ apiName: "Account", label: "Accounts", iconUrl: "...", color: "7f8de1",
style: "background-color:#7f8de1;" }
],
whoObjects: [
{ apiName: "Contact", label: "Contacts", iconUrl: "...", color: "..." },
{ apiName: "Lead", label: "Leads", iconUrl: "...", color: "..." }
],
assignedToObjects: [
{ apiName: "User", label: "Users", iconUrl: "...", color: "..." }
],
statusOptions: [
{ label: "Not Started", value: "Not Started" },
{ label: "In Progress", value: "In Progress" },
...
]
}