LWC Components
/**
* @class ActivityPanel
* @description Root orchestrator LWC for the Activity Panel product. This is the only
* component exposed to the Lightning App Builder and the sole entry point
* placed on record pages.
*
* On load, it performs two async operations in parallel:
* - loadMetadata() — imperative Apex calls to fetch button config
* (Activity_Buttons_Configuration) and default filter
* settings (Default_Activity_Filters) from Custom Metadata.
* - getSchema() — imperative Apex call to resolve the schema model
* (Default_Schema_Model or object/record-type-specific
* override) and store it in this.schemaToUse.
*
* Six wire adapters fire reactively in parallel:
* - getWhatObjects → resolves API names for Related To lookup objects.
* - getObjects('who') → reflects Task.WhoId reference types.
* - getObjects('assignedTo') → reflects Task.OwnerId reference types.
* - getObjectInfos (x3) → resolves SLDS icons, colors, and labels for
* each set of lookup objects, building actionConfig.
* - getUserAndCurrentRecord → resolves logged-in user, timezone, current
* record's object API name, record type developer
* name, and pre-fill values for Who and What.
* - getPicklistValue(Task.Status) → loads Task Status picklist into actionConfig
* for use by the ReOpen modal.
*
* Button routing in handleClick():
* - Buttons with a screenFlowName launch a lightning-flow component inside
* a desktop SLDS modal or a full-screen mobile panel.
* - The reserved names (log_a_call, task, event, note) open the built-in
* c-activity-action-modal with pre-populated activity data.
*
* Button visibility in _filterByVisibility():
* - Each button may carry a hiddenFor array of { objectName, recordTypes[] }.
* - A button is hidden when the current object matches and either recordTypes
* is empty (hidden for all types) or the current record type is in the list.
*
* Filter state is maintained as two parallel sets:
* - selected* — the currently applied values that drive the timeline.
* - pending* — staging values held while the filter modal is open.
* On Apply, pending is validated and committed to selected, and
* filterConfig is rebuilt and pushed down to c-activity-record-logs.
*
* Real-time refresh is achieved by subscribing to the
* /event/Activity_UI_Event__e channel via the EMP API.
* Events are filtered by Parent_Record_Id__c to ignore events from other
* record pages open in other tabs. Supported event types are
* 'Close And Refresh' (closes any open flow, then refreshes after 2 s)
* and 'Refresh' (immediate timeline reload).
*
* Responsive breakpoint is 600 px (MOBILE_BREAKPOINT constant).
* Button labels are suppressed below 520 px component width, measured
* via getBoundingClientRect in renderedCallback.
*
* @dependencies
* Apex : ActivityPanelController.getMetadataValue
* ActivityPanelController.getObjects
* ActivityPanelController.getWhatObjects
* ActivityPanelController.getUserAndCurrentRecord
* ActivityRecordLogsController.getSchema
* ActivityUtils.getPicklistValue
* LDS : lightning/uiObjectInfoApi.getObjectInfos
* EMP : lightning/empApi (subscribe, unsubscribe, onError)
* Child : c-activity-record-logs
* c-activity-action-modal
* lightning-flow
*
* @usage
* <!-- Placed on a Record Page via Lightning App Builder -->
* <c-activity-panel record-id={recordId}></c-activity-panel>
*/
/**
* @class ActivityTimeline (activityRecordLogs)
* @description Child timeline-renderer LWC. Responsible for fetching, deduplicating,
* grouping, sorting, filtering, and rendering all activity records for
* a given record context. Exposes a public @api surface so the parent
* activityPanel can trigger operations imperatively.
*
* Data loading (loadActivityTimeline):
* 1. Calls getActivityRecordConfigs() to obtain all configured
* ActivityRecordConfiguration entries from Custom Metadata.
* 2. Calls getSObjectTypeById() to resolve the host object API name.
* 3. Fires two Promise.all batches in parallel:
* - getCurrentAndParentActivities() per config (direct + parent roll-up)
* - fetchChildActivities() per config (child roll-up)
* 4. Global Id-based deduplication is applied separately to the
* upcomingAndOverdue and pastActivities buckets using Set<id> before
* any processing. This prevents the same activity from appearing
* twice when multiple configs or child traversals return overlapping records.
*
* Processing (processActivityTimeline):
* - Upcoming & Overdue: sorted by sortingDate DESC (null last).
* - Past Activities: grouped into month sections by M/YYYY key,
* each section sorted by sortingDate DESC, sections ordered by
* month DESC. Each section carries a human-readable relativeLabel
* (This Month, Last Month, N Months Ago, N Years Ago).
* - menuDisabled is set to true for any non-Task/Event/ContentNote
* sObject so that read-only custom records cannot be edited or deleted.
*
* Filtering (applyFilters — exposed as @api):
* Filters are applied in a fixed pipeline order on each call:
* 1. filterByDateRange — date boundaries converted to GMT after
* being calculated in the user's timezone.
* 2. filterByAssignedTo — 'My activities' compares assignedTo.recordId
* against loggedInUser.recordId.
* 3. filterByActivityTypes — Task records are matched by subtype
* ('Task' or 'Call'), not by sObjectName alone.
* 4. Sort order — 'Oldest dates first' reverses the default DESC.
* 5. updateVisibleList — applies the compact-view thresholds:
* open activities: show last 2 (or first 2 if oldest-first).
* past activities: show first 4 across sections.
*
* Parent-exposed @api methods:
* - refreshActivityTimeline() — full Apex reload + re-process.
* - applyFilters() — re-filters existing data without Apex.
* - expandActivityTimeline(expand) — toggles isOpen/showMoreIcon on all rows.
* - toggleViewAllActivities(showAll) — switches between compact and full view,
* using slice(-2) for open and
* getFirstNActivities(4) for past.
*
* filterConfig setter auto-triggers applyFilters() on every assignment,
* so the parent only needs to set the property; filtering is implicit.
*
* Navigation uses NavigationMixin.Navigate to standard__recordPage
* for both subject links and related record links in summaries.
*
* @dependencies
* Apex : ActivityRecordLogsController.getActivityRecordConfigs
* ActivityRecordLogsController.getCurrentAndParentActivities
* ActivityRecordLogsController.getChildActivities
* ActivityRecordLogsController.saveTask (task completion checkbox)
* ActivityRecordLogsController.getTaskDetails
* ActivityRecordLogsController.getEventDetails
* ActivityRecordLogsController.getNoteDetails
* ActivityUtils.getSObjectTypeById
* Child : c-activity-action-modal
*
* @usage
* <!-- Instantiated by activityPanel; not placed directly on pages -->
* <c-activity-record-logs
* record-id={recordId}
* action-config={actionConfig}
* filter-config={filterConfig}
* logged-in-user={user}
* user-time-zone={timeZone}
* selected-activity-types-string={selectedActivityTypesString}
* schema-to-use={schemaToUse}>
* </c-activity-record-logs>
*/
/**
* @class ActionModal (activityActionModal)
* @description Schema-driven form LWC for creating, editing, and deleting activity
* records. The complete field layout — including field types, labels,
* sizes, picklist sources, required flags, and dependent field relationships
* — is driven at runtime by the schemaToUse object keyed on the action name.
* No field is hardcoded in the template.
*
* Supported actions and target sObjects:
* 'Log Call' → Task (subtype = Call, insert)
* 'New Task' → Task (subtype = Task, insert)
* 'Edit Task' → Task (update)
* 'Edit Comments' → Task (Description only, update)
* 'Change Date' → Task (ActivityDate only, update)
* 'Change Status' → Task (Status only, update)
* 'Change Priority' → Task (Priority only, update)
* 'Create Follow-Up Task' → Task (insert, pre-filled from source activity)
* 'ReOpen' → Task (Status update; Completed is filtered out)
* 'New Event' → Event (insert)
* 'Edit Event' → Event (update)
* 'Create Follow-Up Event'→ Event (insert, pre-filled from source activity)
* 'New Note' → ContentNote (insert + ContentDocumentLink)
* 'Edit Note' → ContentNote (update)
* 'Delete' → Any (Database.delete by recordId)
*
* Picklist loading (loadPicklistOptions):
* Runs once on connectedCallback. Iterates all isCombobox and
* isCustomCombobox fields in the active schema. For fields with a
* controllingField, calls getDependentPicklistValues() and stores the
* result as Map<controllingValue, List<{label,value}>> in optionsMap.
* For independent fields, calls getPicklistValue() and stores
* List<{label,value}>. isOptionsLoaded is set to true only after all
* fields have resolved, preventing partial renders.
*
* computedFields getter:
* Merges schema field definitions with live formData values on every
* render cycle. Handles startDate/endDate type switching based on
* allDayEvent (date vs datetime). Resolves objects arrays for lookup
* fields from actionConfig. Auto-disables a dependent combobox when
* no options are available for the current controlling value.
*
* Date adjustment:
* Whenever startDate or endDate changes, adjustDateTimes() enforces
* that end > start. For all-day events it equalises the changed date
* to the other; for timed events it offsets the unchanged end by +1 h
* or the unchanged start by -1 h.
*
* Dependent field clearing:
* clearDependentFields() resets any field whose controllingField
* matches the field that just changed, preventing stale dependent values.
*
* Mobile rendering:
* useModalView = true when NOT mobile OR when action is 'Delete'.
* Delete always uses the modal on mobile to avoid a disruptive full-screen
* confirmation. All other actions on mobile use a fixed full-screen panel
* with a sticky header and footer.
*
* On save success, dispatches a 'closemodal' CustomEvent with
* { detail: { refresh: true } }. On cancel, dispatches with
* { detail: { refresh: false } }.
*
* @dependencies
* Apex : ActivityRecordLogsController.saveTask
* ActivityRecordLogsController.saveEvent
* ActivityRecordLogsController.saveNote
* ActivityRecordLogsController.deleteActivity
* ActivityUtils.getPicklistValue
* ActivityUtils.getDependentPicklistValues
* Child : c-record-lookup
*
* @usage
* <!-- Instantiated by activityPanel and activityRecordLogs; not placed directly -->
* <c-activity-action-modal
* action={action}
* activity={activity}
* action-config={actionConfig}
* current-record-id={recordId}
* schema-to-use={schemaToUse}
* onclosemodal={handleCloseModal}>
* </c-activity-action-modal>
*/
/**
* @class RecordLookup
* @description Reusable custom lookup field LWC that supports multi-object search
* with a debounced live search, entity-type switcher, selected-record
* pill, and standard LWC validity API (reportValidity / checkValidity).
*
* Object switching:
* The objects @api property accepts an array of view-model objects
* (apiName, label, iconUrl, color) built by activityPanel from
* getObjectInfos wire data. A dropdown icon next to the search input
* allows the user to switch the active search object. On set, the
* setter calls initSelectionFromProps() to either restore a pre-selected
* value from fieldValue or default to the object matching defaultEntity.
*
* Search behaviour:
* - Input is ignored until searchTerm.length >= 2 to avoid
* unnecessary Apex calls on single-character entry.
* - A 250 ms debounce prevents a query on every keystroke.
* - getSearchedRecords() performs a SOQL LIKE query on the
* server, respecting Record_Lookup_Configuration display field
* overrides (e.g. CaseNumber instead of Name).
* - Results are capped at 20 records server-side.
*
* Selection and clearing:
* - Selecting a record shows a pill and dispatches a 'change' event
* with { recordId, name, sObjectType } in event.detail.value.
* - Clearing the pill dispatches 'change' with all null values,
* signalling the parent to unset the field.
*
* Validity:
* - reportValidity() sets this.showError = true and returns false
* when required = true and no record is selected. This mirrors
* the lightning-input validity contract so activityActionModal
* can query this component alongside native inputs.
* - checkValidity() performs the same check with no side effects.
*
* NavigationMixin is imported but not actively used for navigation;
* it is included as a foundation for potential future use.
*
* @dependencies
* Apex : ActivityRecordLogsController.getSearchedRecords
*
* @usage
* <!-- Instantiated by activityActionModal for who, what, assignedTo fields -->
* <c-record-lookup
* objects={field.objects}
* default-entity={field.defaultEntity}
* field-label={field.label}
* field-value={field.value}
* data-name={field.name}
* required={field.required}
* onchange={handleDetailChange}>
* </c-record-lookup>
*/
Apex Classes
/**
* @class ActivityPanelController
* @description AuraEnabled controller that serves the activityPanel LWC.
* Handles three responsibilities: Custom Metadata retrieval,
* lookup-object API name resolution, and current record context resolution.
*
* getMetadataValue(devName):
* Reads a single named entry from the shared mapActivityConfigurations
* map (populated by ActivityUtils). Returns a JSON string shaped as
* { isSuccess: true, <devName>: "<raw JSON value>" } on success,
* or { isSuccess: false, errorMessage: "..." } on failure or missing key.
* Not cacheable because Custom Metadata values may be updated by admins.
*
* getObjects(fieldName):
* Uses Schema reflection to read the referenceTo types of Task.WhoId
* ('who') or Task.OwnerId ('assignedTo'). Returns the list to drive
* the lightning/uiObjectInfoApi wire adapter in the LWC. Cacheable.
*
* getWhatObjects(devName):
* Reads the Related_To_Objects comma-delimited string from the
* Activity_Record_Configuration metadata entry and splits it into a
* List<String>. Cacheable.
*
* getUserAndCurrentRecord(recordId):
* The most complex method. Resolves five values in a single call to
* minimise round-trips:
* 1. loggedInUser — built from UserInfo.
* 2. userTimeZone — IANA timezone ID from UserInfo.
* 3. objectApiName — derived from the record Id prefix.
* 4. recordTypeDeveloperName — queried from the record itself.
* 5. who / what — pre-fill references for new activity forms.
*
* Who/What resolution follows this precedence:
* - If Record_Lookup_Configuration defines a whoIdField for the
* object, that field's value on the record is used as Who.
* - If the field is blank but the object itself is in the whoSet
* (Contact, Lead), the record itself becomes Who.
* - If no whoIdField is configured but the object is in whoSet,
* the record becomes Who directly.
* - Same pattern applies to What, using whatIdField and whatSet
* (Related_To_Objects list).
*
* deriveDisplayPath(idField) is a private helper that converts a
* lookup field API name to its standard Name traversal path
* (e.g. 'AccountId' → 'Account.Name', custom '__c' → '__r.Name').
*
* @sharing with sharing — respects record-level security.
*
* @usage
* // Called via @wire in activityPanel.js
* @wire(getUserAndCurrentRecord, { recordId: '$recordId' })
* wiredUserAndCurrentRecord({ data, error }) { ... }
*/
/**
* @class ActivityRecordLogsController
* @description The core data layer for the Activity Panel product. Serves the
* activityRecordLogs and activityActionModal LWCs. Handles five concerns:
* schema resolution, activity fetching (direct + parent + child roll-up),
* record search, CRUD operations, and type-safe field conversion.
*
* getSchema(recordId):
* Resolves which schema model JSON to use for the current record context.
* Resolution order:
* 1. If the object has no active (non-master) record types, query
* Activity_Panel_Configuration__mdt by objectName alone.
* 2. If the object has active record types, build a lookup set with
* three keys: objectName::RecordTypeName, objectName::DeveloperName,
* and objectName. Query for any matching metadata record.
* 3. If no specific entry found, fall back to the
* 'Default Schema Model' metadata record.
* Returns JSON: { isSuccess: true, sMessage: "<schema JSON>" }.
*
* getActivityRecordConfigs():
* Deserializes the Activity_Record_Configuration array from Custom Metadata
* into List<ActivityRecordConfiguration> inner class instances.
*
* getCurrentAndParentActivities(recordId, configJson):
* Dispatches to getTasks(), getEvents(), getNotes(), or getCustomRecords()
* based on config.sObjectName. Each method:
* - Queries the host record with any configured parentFields.
* - Classifies the record into setWhoId or setWhatId based on its type.
* - Follows each parentField value and classifies those Ids as well.
* - Builds a WHERE clause covering both WhoId IN and WhatId IN sets.
* - For ContentNote, resolves ContentDocumentLink records first, then
* queries ContentNote WHERE Id IN docIds.
* - Task records with status 'Completed' or non-null CompletedDateTime
* go to pastActivities; all others go to upcomingAndOverdue.
* - Event records whose startDate is in the past go to pastActivities.
*
* getChildActivities(recordId, configJson, childObjectName, relationField, whereClause):
* Queries the child object using the relationField to collect related Ids,
* then builds the same Who/What set as getCurrentAndParentActivities and
* fetches activities for those Ids.
*
* saveTask / saveEvent / saveNote:
* Accept a JSON string, deserialize to a Map<String,Object>, and
* use Schema.DescribeFieldResult + convertValue() to coerce each
* value to the correct Apex type before putting it on the SObject.
* This avoids hardcoded field mappings: any field in the schema JSON
* is automatically handled as long as it exists on the object.
* Upsert logic: if the map contains 'Id', the record is updated;
* otherwise it is inserted. For ContentNote, an insert also creates
* a ContentDocumentLink to the relatedToId with ShareType 'V'.
*
* convertValue(value, fieldDescribe):
* Type-switch on Schema.DisplayType. Handles Date, DateTime, Boolean,
* Double/Currency/Percent, Integer, BASE64. Falls through to string for
* all other types including Picklist, Lookup, and TextArea.
*
* getSearchedRecords(sObjectName, searchTerm):
* Builds a dynamic SOQL LIKE query. Consults Record_Lookup_Configuration
* to resolve the correct display field for each object (e.g. CaseNumber
* instead of Name). Returns List<Map<String,String>> with Id and Name.
*
* deleteActivity(recordId):
* Calls Database.delete. Throws AuraHandledException on failure so
* the LWC can display a user-facing error message.
*
* Inner classes (defined at end of file):
* ActivityRecordConfiguration, ObjectConfig, ChildConfig, TemplateItem.
*
* @sharing with sharing — respects record-level security.
*
* @usage
* // Imperative call from activityRecordLogs.js
* const configs = await getActivityRecordConfigs();
* const result = await getCurrentAndParentActivities({
* recordId: this.recordId,
* configJson: JSON.stringify(config)
* });
*/
/**
* @class ActivityWrapper
* @description Global data transfer object (DTO) container for the Activity Panel
* product. Holds no business logic — all fields are @AuraEnabled so
* they are automatically serialized to JSON when returned from
* AuraEnabled methods and deserialized into JavaScript objects in LWCs.
*
* Top-level class fields (returned by getUserAndCurrentRecord):
* loggedInUser — RelatedRecord for the running user.
* who — Pre-fill value for the Name (Who) field.
* what — Pre-fill value for the Related To (What) field.
* userTimeZone — IANA timezone string (e.g. 'America/Los_Angeles').
* objectApiName — API name of the current page's host object.
* recordTypeDeveloperName — Developer name of the record's record type.
*
* Inner classes:
*
* RelatedRecord
* Lightweight reference to any Salesforce record.
* Used for who, what, assignedTo, and loggedInUser across the product.
* Fields: recordId, name, sObjectType.
*
* ActivityCollection
* Return type for all activity-fetch methods in
* ActivityRecordLogsController. Initialises both lists in its
* constructor so callers never need to null-check.
* Fields: upcomingAndOverdue List<ActivityRecord>,
* pastActivities List<ActivityRecord>.
*
* ActivityRecord
* Full UI-ready representation of a single activity row.
* Carries all fields needed by the timeline template including
* computed display properties (icon, bgColor, iconColor, dateLabel,
* dateClass, showMoreIcon, isOpen, menuButtons) alongside the raw
* record data (subject, status, priority, dates, description).
* Event-specific fields (location, startDate, endDate, allDayEvent)
* are present on all instances but populated only for Event records.
*
* ActivityDetail
* Represents a single token in a summaryTemplate or detailTemplate
* row. The label field is populated only for detail rows. The
* recordId field, when present, causes the LWC to render the value
* as a clickable Salesforce navigation link. The href field, when
* present, renders an external anchor (mailto:, tel:). The scrollable
* flag causes the LWC to wrap the value in a max-height container.
*
* ActivitySection
* Groups ActivityRecord instances into a collapsible section.
* Used by the LWC to render month groups in the past-activities
* list. Carries sectionLabel (e.g. 'May 2026'), relativeLabel
* (e.g. 'This Month'), isOpen, and icon (chevron indicator).
*
* ComboBoxOption
* Simple label/value pair returned by ActivityUtils picklist methods.
* Serializes to the { label, value } shape expected by
* lightning-combobox and lightning-radio-group.
*
* @usage
* // Returned from Apex, automatically deserialized in LWC
* @wire(getUserAndCurrentRecord, { recordId: '$recordId' })
* wiredUserAndCurrentRecord({ data, error }) {
* if (data) {
* this.user = data.loggedInUser; // ActivityWrapper.RelatedRecord
* this.who = data.who;
* this.timeZone = data.userTimeZone;
* }
* }
*/
/**
* @class ActivityUtils
* @description Shared utility library for the Activity Panel product. Contains
* no AuraEnabled query methods of its own except getPicklistValue,
* getDependentPicklistValues, and getSObjectTypeById. All other methods
* are called internally by ActivityRecordLogsController and
* ActivityPanelController to avoid code duplication.
*
* mapActivityConfigurations (static lazy-loaded property):
* The single point of truth for all Custom Metadata values across
* the product. Loaded once per transaction from
* Activity_Panel_Configuration__mdt and cached as Map<DeveloperName, Value__c>.
* Both ActivityPanelController and ActivityRecordLogsController
* reference this map via a private static final field, so all
* metadata reads within the same transaction share the same map instance.
*
* createActivityFromTask(task):
* Builds a fully populated ActivityWrapper.ActivityRecord from a Task
* SObject. Sets icon to 'standard:task' or 'standard:log_a_call' based
* on TaskSubtype. Sets bgColor to #4abf75 (Task) or #48c3cc (Call).
* Constructs the default menuButtons list including all task-specific
* context menu actions.
*
* createActivityFromEvent(event):
* Builds an ActivityWrapper.ActivityRecord from an Event SObject.
* Sets icon to 'standard:event', bgColor to #cb65ff.
* Menu buttons are limited to Edit and Delete.
*
* buildTaskEventSummary(activity, isPast, recordId):
* Builds the inline summary sentence tokens (List<ActivityDetail>)
* for Task and Event records. Uses ActivityWrapper.RelatedRecord
* references already populated on the ActivityRecord. Shows 'You'
* for the running user, and omits Who and What links when their
* recordId matches the current page's recordId (avoids self-referential
* links on the record being viewed).
*
* buildDetails(oRec, lstTemplateItem):
* Processes a List<TemplateItem> from Custom Metadata config into
* a List<ActivityDetail> for either summaryTemplate or detailTemplate.
* Delegates to processTemplateItem() which handles all six types:
* text, record, owner, conditionalText, conditionalGroup, link.
* conditionalGroup is recursive — its nested items are each passed
* back through processTemplateItem().
*
* buildSubject(oRec, subjectTemplate):
* Resolves the activity subject string from a TemplateItem.
* Supports 'text' (field read or static) and 'conditionalText'
* (map lookup by dependsOn field value). Returns '[No Subject]'
* as a safe fallback.
*
* getFieldValueAsString(record, fieldPath):
* Traverses dot-notation paths on SObjects by splitting on '.'
* and calling getSObject() for each intermediate relationship
* before calling get() on the terminal field. Returns null safely
* if any intermediate relationship is null.
*
* formatDateTime(sortingDate, showTime, defaultValue, isAllDayEvent):
* Produces human-readable date labels: 'Today', 'Tomorrow',
* 'Yesterday', or 'MMM d' format for all other dates.
* When showTime = true, prepends the time in 'hh:mm a' format
* or 'All-Day Event' for all-day events, separated by ' | '.
*
* getDependentPicklistValues(sObjectName, controllingFieldName, dependentFieldName):
* Decodes the Salesforce picklist validFor base64 bitmask to map
* each dependent value to its controlling parent values.
* Returns Map<controllingValue, List<ComboBoxOption>> as JSON.
*
* @usage
* // Access the shared metadata map from any controller
* private static final Map<String,String> configs = ActivityUtils.mapActivityConfigurations;
*
* // Build an activity record from a queried Task
* ActivityWrapper.ActivityRecord activity = ActivityUtils.createActivityFromTask(oTask);
*
* // Resolve a dot-notation field path
* String ownerName = ActivityUtils.getFieldValueAsString(record, 'Owner.Name');
*/
/**
* @class InvocableCreateActivityUIEvent
* @description Global invocable Apex class that allows Salesforce Flows to trigger
* real-time UI updates on the Activity Panel component by publishing
* Activity_UI_Event__e platform events via EventBus.publish().
*
* This class is the integration bridge between Flow automation and the
* EMP API subscription in activityPanel.js. Without it, a Flow that
* creates or updates an activity would have no way to notify the
* already-rendered Activity Panel to refresh its timeline.
*
* Input wrapper (ActivityEventWrapper):
* parentRecordId (required) — 18-character Id of the record page
* hosting the Activity Panel. The LWC
* uses this to filter out events from
* other record pages in other browser tabs.
* eventType (required) — Controls the action taken by the LWC:
* 'Close And Refresh' — closes any open
* Screen Flow panel on the Activity Panel,
* then refreshes the timeline after 2 seconds.
* 'Refresh' — immediately triggers a full
* timeline reload without closing any UI.
*
* The method accepts a List<ActivityEventWrapper> to comply with the
* Invocable framework's bulk input requirement. All wrappers in the
* list are published in a single EventBus.publish() call to avoid
* unnecessary DML statements.
*
* Exceptions are caught and written to debug logs rather than re-thrown
* so that a publishing failure does not interrupt the calling Flow.
*
* @sharing with sharing — no record queries are performed; sharing has no effect.
*
* @usage
* // From Flow Builder — add an Action element, search for:
* // "Create Activity UI Event"
* //
* // Input variables:
* // parentRecordId = {!recordId} (the current record page Id)
* // eventType = "Close And Refresh" (or "Refresh")
*
* // From Apex (e.g. test or batch):
* InvocableCreateActivityUIEvent.ActivityEventWrapper w =
* new InvocableCreateActivityUIEvent.ActivityEventWrapper();
* w.parentRecordId = recordId;
* w.eventType = 'Refresh';
* InvocableCreateActivityUIEvent.createActivityUIEvent(
* new List<InvocableCreateActivityUIEvent.ActivityEventWrapper>{ w }
* );
*/