cmEngage Hub

Technical Documentation

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" },
    ...
  ]
}