@@ -5389,11 +5389,55 @@ function clearNewRoomFormError() {
53895389 } ) ;
53905390}
53915391
5392+ /**
5393+ * Builds optimistic and failure rollback data for adding invitees to a report's participants.
5394+ * Skips IDs that already exist in participants to avoid overwriting their settings.
5395+ * On failure, only nulls out the newly added keys so Onyx removes them without overwriting
5396+ * concurrent participant changes.
5397+ */
5398+ function buildParticipantsInviteData (
5399+ targetReport : OnyxEntry < Report > ,
5400+ inviteeAccountIDs : number [ ] ,
5401+ ) : { optimistic : Pick < Report , 'participants' > ; failure : Pick < Report , 'participants' > } | undefined {
5402+ if ( ! targetReport || inviteeAccountIDs . length === 0 ) {
5403+ return undefined ;
5404+ }
5405+
5406+ const defaultPref = getDefaultNotificationPreferenceForReport ( targetReport ) ;
5407+ const participantsAfterInvitation = inviteeAccountIDs . reduce (
5408+ ( acc : Participants , accountID : number ) => {
5409+ if ( accountID in ( targetReport . participants ?? { } ) ) {
5410+ return acc ;
5411+ }
5412+ // eslint-disable-next-line no-param-reassign -- Mutating the reduce accumulator is intentional
5413+ acc [ accountID ] = {
5414+ notificationPreference : defaultPref ,
5415+ role : CONST . REPORT . ROLE . MEMBER ,
5416+ } ;
5417+ return acc ;
5418+ } ,
5419+ { ...targetReport . participants } ,
5420+ ) ;
5421+
5422+ const rollback : Record < number , null > = { } ;
5423+ for ( const accountID of inviteeAccountIDs ) {
5424+ if ( ! ( accountID in ( targetReport . participants ?? { } ) ) ) {
5425+ rollback [ accountID ] = null ;
5426+ }
5427+ }
5428+
5429+ return {
5430+ optimistic : { participants : participantsAfterInvitation } ,
5431+ failure : { participants : rollback as unknown as Participants } ,
5432+ } ;
5433+ }
5434+
53925435function resolveActionableMentionWhisper (
53935436 report : OnyxEntry < Report > ,
53945437 reportAction : OnyxEntry < ReportAction > ,
53955438 resolution : ValueOf < typeof CONST . REPORT . ACTIONABLE_MENTION_WHISPER_RESOLUTION > | ValueOf < typeof CONST . REPORT . ACTIONABLE_MENTION_INVITE_TO_SUBMIT_EXPENSE_CONFIRM_WHISPER > ,
53965439 isReportArchived : boolean | undefined ,
5440+ parentReport ?: OnyxEntry < Report > ,
53975441) {
53985442 const reportID = report ?. reportID ;
53995443 if ( ! reportAction || ! reportID ) {
@@ -5426,6 +5470,22 @@ function resolveActionableMentionWhisper(
54265470 lastActorAccountID : report . lastActorAccountID ,
54275471 } ;
54285472
5473+ // When the resolution is 'invited', optimistically add the invited users to report.participants
5474+ // so the members list updates immediately without waiting for the server response.
5475+ const isInviteResolution = resolution === CONST . REPORT . ACTIONABLE_MENTION_WHISPER_RESOLUTION . INVITE ;
5476+ const originalMessage = ReportActionsUtils . getOriginalMessage ( reportAction as ReportAction < typeof CONST . REPORT . ACTIONS . TYPE . ACTIONABLE_MENTION_WHISPER > ) ;
5477+ const inviteeAccountIDs = isInviteResolution ? ( originalMessage ?. inviteeAccountIDs ?? [ ] ) : [ ] ;
5478+
5479+ const participantsInviteData = isInviteResolution && report ? buildParticipantsInviteData ( report , inviteeAccountIDs ) : undefined ;
5480+ const participantsOptimisticData = participantsInviteData ?. optimistic ;
5481+ const participantsFailureData = participantsInviteData ?. failure ;
5482+
5483+ // When the action belongs to a child report (e.g. a one-transaction thread), also update
5484+ // the parent report's participants so the members list the user is viewing updates immediately.
5485+ const parentInviteData = isInviteResolution && parentReport ?. reportID && parentReport . reportID !== reportID ? buildParticipantsInviteData ( parentReport , inviteeAccountIDs ) : undefined ;
5486+ const parentParticipantsOptimisticData = parentInviteData ?. optimistic ;
5487+ const parentParticipantsFailureData = parentInviteData ?. failure ;
5488+
54295489 const optimisticData : Array < OnyxUpdate < typeof ONYXKEYS . COLLECTION . REPORT_ACTIONS | typeof ONYXKEYS . COLLECTION . REPORT > > = [
54305490 {
54315491 onyxMethod : Onyx . METHOD . MERGE ,
@@ -5442,10 +5502,21 @@ function resolveActionableMentionWhisper(
54425502 {
54435503 onyxMethod : Onyx . METHOD . MERGE ,
54445504 key : `${ ONYXKEYS . COLLECTION . REPORT } ${ reportID } ` ,
5445- value : reportUpdateDataWithPreviousLastMessage ,
5505+ value : {
5506+ ...reportUpdateDataWithPreviousLastMessage ,
5507+ ...participantsOptimisticData ,
5508+ } ,
54465509 } ,
54475510 ] ;
54485511
5512+ if ( parentParticipantsOptimisticData && parentReport ?. reportID ) {
5513+ optimisticData . push ( {
5514+ onyxMethod : Onyx . METHOD . MERGE ,
5515+ key : `${ ONYXKEYS . COLLECTION . REPORT } ${ parentReport . reportID } ` ,
5516+ value : parentParticipantsOptimisticData ,
5517+ } ) ;
5518+ }
5519+
54495520 const failureData : Array < OnyxUpdate < typeof ONYXKEYS . COLLECTION . REPORT_ACTIONS | typeof ONYXKEYS . COLLECTION . REPORT > > = [
54505521 {
54515522 onyxMethod : Onyx . METHOD . MERGE ,
@@ -5462,10 +5533,21 @@ function resolveActionableMentionWhisper(
54625533 {
54635534 onyxMethod : Onyx . METHOD . MERGE ,
54645535 key : `${ ONYXKEYS . COLLECTION . REPORT } ${ reportID } ` ,
5465- value : reportUpdateDataWithCurrentLastMessage , // revert back to the current report last message data in case of failure
5536+ value : {
5537+ ...reportUpdateDataWithCurrentLastMessage , // revert back to the current report last message data in case of failure
5538+ ...participantsFailureData ,
5539+ } ,
54665540 } ,
54675541 ] ;
54685542
5543+ if ( parentParticipantsFailureData && parentReport ?. reportID ) {
5544+ failureData . push ( {
5545+ onyxMethod : Onyx . METHOD . MERGE ,
5546+ key : `${ ONYXKEYS . COLLECTION . REPORT } ${ parentReport . reportID } ` ,
5547+ value : parentParticipantsFailureData ,
5548+ } ) ;
5549+ }
5550+
54695551 const parameters : ResolveActionableMentionWhisperParams = {
54705552 reportActionID : reportAction . reportActionID ,
54715553 resolution,
0 commit comments