Repository: element-hq/element-x-android (language: kotlin)

Issue:
Room alias autocomplete not working
### Steps to reproduce

1. (Turn off wysiwyg because you can't type MD in that)
2. Type #
3. Optionally continue typing a known alias

### Outcome

#### What did you expect?
Alias suggestions like @ uid suggestions 
#### What happened instead?
Nothing

### Your phone model

_No response_

### Operating system version

android 13

### Application version and app store

schildi next based on 0.4.13

### Homeserver

sypanse 1.106.0

### Will you send logs?

No

### Are you willing to provide a PR?

No


Discussion hints:
nan

Pull request: Suggestion for room alias (disabled for now)
<!-- Please read [CONTRIBUTING.md](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
 
## Content

<!-- Describe shortly what has been changed -->

- implement completion for room alias, under a disabled feature flag (mainly to wait for the feature to be implemented on iOS)
- Rename `Mention` to `IntentionalMention` for clarity
- Remove dead code, there is no intentional mention for Room or RoomAlias.
- Rename `IntentionalMention.AtRoom` to `IntentionalMention.Room` to match Rust naming

## Motivation and context

<!-- Provide link to the corresponding issue if applicable or explain the context -->
Closes #2907

## Screenshots / GIFs

<!--
We have screenshot tests in the project, so attaching screenshots to a PR is not mandatory, as far as there
is a Composable Preview covering the changes. In this case, the change will appear in the file diff.
Note that all the UI composables should be covered by a Composable Preview.

Providing a video of the change is still very useful for the reviewer and for the history of the project.

You can use a table like this to show screenshots comparison.
Uncomment this markdown table below and edit the last line `|||`:
|copy screenshot of before here|copy screenshot of after here|

|Before|After|
|-|-|
|||
 -->

![RoomAliasCompletion](https://github.com/user-attachments/assets/976e4f8c-d76e-43a3-9edc-cfe1ef45c77e)


## Tests

<!-- Explain how you tested your development -->

- Enable the feature `Room alias suggestions` in the developer settings
- With an account which has rooms which has canonical aliases, start typing `#` in the composer of any room. You should see completion for room alias appearing.

## Tested devices

- [ ] Physical
- [x] Emulator
- OS version(s):

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 23
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [ ] Pull request is based on the develop branch
- [ ] Pull request title will be used in the release note, it clearly define what will change for the user
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off)
- [ ] You've made a self review of your PR


Test command: ./gradlew :features:messages:impl:testDebugUnitTest --tests "io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenterTest" --tests "io.element.android.features.messages.impl.timeline.TimelineControllerTest" --tests "io.element.android.features.messages.impl.MessagesPresenterTest" --tests "io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessorTest" --tests "io.element.android.features.messages.impl.messagecomposer.DefaultRoomAliasSuggestionsDataSourceTest" :libraries:push:impl:testDebugUnitTest --tests "io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiverHandlerTest" :libraries:textcomposer:impl:testDebugUnitTest --tests "io.element.android.libraries.textcomposer.impl.model.MarkdownTextEditorStateTest" --tests "io.element.android.libraries.textcomposer.impl.mentions.IntentionalMentionSpanProviderTest" --tests "io.element.android.libraries.textcomposer.impl.components.markdown.MarkdownTextInputTest"

Fix patch:
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
index df6aa26778a..f1f8b844ef0 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
@@ -66,11 +66,11 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
 import io.element.android.features.messages.impl.actionlist.ActionListView
 import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
 import io.element.android.features.messages.impl.attachments.Attachment
-import io.element.android.features.messages.impl.mentions.MentionSuggestionsPickerView
 import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
 import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
 import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
 import io.element.android.features.messages.impl.messagecomposer.MessageComposerView
+import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView
 import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
 import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
 import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
@@ -377,7 +377,7 @@ private fun MessagesViewContent(
                 @Composable {}
             },
             sheetSwipeEnabled = state.composerState.showTextFormatting,
-            sheetShape = if (state.composerState.showTextFormatting || state.composerState.memberSuggestions.isNotEmpty()) {
+            sheetShape = if (state.composerState.showTextFormatting || state.composerState.suggestions.isNotEmpty()) {
                 MaterialTheme.shapes.large
             } else {
                 RectangleShape
@@ -427,7 +427,7 @@ private fun MessagesViewContent(
             },
             sheetContentKey = sheetResizeContentKey.intValue,
             sheetTonalElevation = 0.dp,
-            sheetShadowElevation = if (state.composerState.memberSuggestions.isNotEmpty()) 16.dp else 0.dp,
+            sheetShadowElevation = if (state.composerState.suggestions.isNotEmpty()) 16.dp else 0.dp,
         )
     }
 }
@@ -439,7 +439,7 @@ private fun MessagesViewComposerBottomSheetContents(
 ) {
     if (state.userEventPermissions.canSendMessage) {
         Column(modifier = Modifier.fillMaxWidth()) {
-            MentionSuggestionsPickerView(
+            SuggestionsPickerView(
                 modifier = Modifier
                     .heightIn(max = 230.dp)
                     // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
@@ -451,9 +451,9 @@ private fun MessagesViewComposerBottomSheetContents(
                 roomId = state.roomId,
                 roomName = state.roomName.dataOrNull(),
                 roomAvatarData = state.roomAvatar.dataOrNull(),
-                memberSuggestions = state.composerState.memberSuggestions,
+                suggestions = state.composerState.suggestions,
                 onSelectSuggestion = {
-                    state.composerState.eventSink(MessageComposerEvents.InsertMention(it))
+                    state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
                 }
             )
             MessageComposerView(
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
index 1f6ae7c7f49..d5f34294507 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt
@@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
 
 import android.net.Uri
 import androidx.compose.runtime.Immutable
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MessageComposerMode
 import io.element.android.libraries.textcomposer.model.Suggestion
 
@@ -44,6 +44,6 @@ sealed interface MessageComposerEvents {
     data class Error(val error: Throwable) : MessageComposerEvents
     data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents
     data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents
-    data class InsertMention(val mention: ResolvedMentionSuggestion) : MessageComposerEvents
+    data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvents
     data object SaveDraft : MessageComposerEvents
 }
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
index d0e50d114c3..8f01f15542d 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
@@ -40,7 +40,7 @@ import im.vector.app.features.analytics.plan.Interaction
 import io.element.android.features.messages.impl.attachments.Attachment
 import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
 import io.element.android.features.messages.impl.draft.ComposerDraftService
-import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor
+import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
 import io.element.android.features.messages.impl.timeline.TimelineController
 import io.element.android.features.messages.impl.utils.TextPillificationHelper
 import io.element.android.libraries.architecture.Presenter
@@ -55,8 +55,8 @@ import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
 import io.element.android.libraries.matrix.api.permalink.PermalinkData
 import io.element.android.libraries.matrix.api.permalink.PermalinkParser
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.MatrixRoom
-import io.element.android.libraries.matrix.api.room.Mention
 import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
 import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
 import io.element.android.libraries.matrix.api.room.isDm
@@ -72,7 +72,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
 import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
 import io.element.android.libraries.textcomposer.mentions.LocalMentionSpanTheme
 import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
 import io.element.android.libraries.textcomposer.model.Message
 import io.element.android.libraries.textcomposer.model.MessageComposerMode
@@ -117,6 +117,7 @@ class MessageComposerPresenter @Inject constructor(
     private val analyticsService: AnalyticsService,
     private val messageComposerContext: DefaultMessageComposerContext,
     private val richTextEditorStateFactory: RichTextEditorStateFactory,
+    private val roomAliasSuggestionsDataSource: RoomAliasSuggestionsDataSource,
     private val permalinkParser: PermalinkParser,
     private val permalinkBuilder: PermalinkBuilder,
     permissionsPresenterFactory: PermissionsPresenter.Factory,
@@ -125,6 +126,7 @@ class MessageComposerPresenter @Inject constructor(
     private val mentionSpanProvider: MentionSpanProvider,
     private val pillificationHelper: TextPillificationHelper,
     private val roomMemberProfilesCache: RoomMemberProfilesCache,
+    private val suggestionsProcessor: SuggestionsProcessor,
 ) : Presenter<MessageComposerState> {
     private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
     private var pendingEvent: MessageComposerEvents? = null
@@ -149,8 +151,10 @@ class MessageComposerPresenter @Inject constructor(
         }
         val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false)
         var isMentionsEnabled by remember { mutableStateOf(false) }
+        var isRoomAliasSuggestionsEnabled by remember { mutableStateOf(false) }
         LaunchedEffect(Unit) {
             isMentionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.Mentions)
+            isRoomAliasSuggestionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.RoomAliasSuggestions)
         }
 
         val cameraPermissionState = cameraPermissionPresenter.present()
@@ -189,6 +193,8 @@ class MessageComposerPresenter @Inject constructor(
 
         val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
 
+        val roomAliasSuggestions by roomAliasSuggestionsDataSource.getAllRoomAliasSuggestions().collectAsState(initial = emptyList())
+
         LaunchedEffect(attachmentsState.value) {
             when (val attachmentStateValue = attachmentsState.value) {
                 is AttachmentsState.Sending.Processing -> {
@@ -212,7 +218,7 @@ class MessageComposerPresenter @Inject constructor(
             }
         }
 
-        val memberSuggestions = remember { mutableStateListOf<ResolvedMentionSuggestion>() }
+        val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() }
         LaunchedEffect(isMentionsEnabled) {
             if (!isMentionsEnabled) return@LaunchedEffect
             val currentUserId = room.sessionId
@@ -228,15 +234,16 @@ class MessageComposerPresenter @Inject constructor(
             val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
             merge(mentionStartTrigger, mentionCompletionTrigger)
                 .combine(room.membersStateFlow) { suggestion, roomMembersState ->
-                    memberSuggestions.clear()
-                    val result = MentionSuggestionsProcessor.process(
+                    suggestions.clear()
+                    val result = suggestionsProcessor.process(
                         suggestion = suggestion,
                         roomMembersState = roomMembersState,
+                        roomAliasSuggestions = if (isRoomAliasSuggestionsEnabled) roomAliasSuggestions else emptyList(),
                         currentUserId = currentUserId,
                         canSendRoomMention = ::canSendRoomMention,
                     )
                     if (result.isNotEmpty()) {
-                        memberSuggestions.addAll(result)
+                        suggestions.addAll(result)
                     }
                 }
                 .collect()
@@ -362,22 +369,27 @@ class MessageComposerPresenter @Inject constructor(
                 is MessageComposerEvents.SuggestionReceived -> {
                     suggestionSearchTrigger.value = event.suggestion
                 }
-                is MessageComposerEvents.InsertMention -> {
+                is MessageComposerEvents.InsertSuggestion -> {
                     localCoroutineScope.launch {
                         if (showTextFormatting) {
-                            when (val mention = event.mention) {
-                                is ResolvedMentionSuggestion.AtRoom -> {
+                            when (val suggestion = event.resolvedSuggestion) {
+                                is ResolvedSuggestion.AtRoom -> {
                                     richTextEditorState.insertAtRoomMentionAtSuggestion()
                                 }
-                                is ResolvedMentionSuggestion.Member -> {
-                                    val text = mention.roomMember.userId.value
-                                    val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch
+                                is ResolvedSuggestion.Member -> {
+                                    val text = suggestion.roomMember.userId.value
+                                    val link = permalinkBuilder.permalinkForUser(suggestion.roomMember.userId).getOrNull() ?: return@launch
+                                    richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
+                                }
+                                is ResolvedSuggestion.Alias -> {
+                                    val text = suggestion.roomAlias.value
+                                    val link = permalinkBuilder.permalinkForRoomAlias(suggestion.roomAlias).getOrNull() ?: return@launch
                                     richTextEditorState.insertMentionAtSuggestion(text = text, link = link)
                                 }
                             }
-                        } else if (markdownTextEditorState.currentMentionSuggestion != null) {
-                            markdownTextEditorState.insertMention(
-                                mention = event.mention,
+                        } else if (markdownTextEditorState.currentSuggestion != null) {
+                            markdownTextEditorState.insertSuggestion(
+                                resolvedSuggestion = event.resolvedSuggestion,
                                 mentionSpanProvider = mentionSpanProvider,
                                 permalinkBuilder = permalinkBuilder,
                             )
@@ -417,7 +429,7 @@ class MessageComposerPresenter @Inject constructor(
             canShareLocation = canShareLocation.value,
             canCreatePoll = canCreatePoll.value,
             attachmentsState = attachmentsState.value,
-            memberSuggestions = memberSuggestions.toPersistentList(),
+            suggestions = suggestions.toPersistentList(),
             resolveMentionDisplay = resolveMentionDisplay,
             eventSink = { handleEvents(it) },
         )
@@ -432,17 +444,21 @@ class MessageComposerPresenter @Inject constructor(
         // Reset composer right away
         resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = capturedMode is MessageComposerMode.Edit)
         when (capturedMode) {
-            is MessageComposerMode.Normal -> room.sendMessage(body = message.markdown, htmlBody = message.html, mentions = message.mentions)
+            is MessageComposerMode.Normal -> room.sendMessage(
+                body = message.markdown,
+                htmlBody = message.html,
+                intentionalMentions = message.intentionalMentions
+            )
             is MessageComposerMode.Edit -> {
                 val eventId = capturedMode.eventId
                 val transactionId = capturedMode.transactionId
                 timelineController.invokeOnCurrentTimeline {
                     // First try to edit the message in the current timeline
-                    editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
+                    editMessage(eventId, transactionId, message.markdown, message.html, message.intentionalMentions)
                         .onFailure { cause ->
                             if (cause is TimelineException.EventNotFound && eventId != null) {
                                 // if the event is not found in the timeline, try to edit the message directly
-                                room.editMessage(eventId, message.markdown, message.html, message.mentions)
+                                room.editMessage(eventId, message.markdown, message.html, message.intentionalMentions)
                             }
                         }
                 }
@@ -450,7 +466,7 @@ class MessageComposerPresenter @Inject constructor(
 
             is MessageComposerMode.Reply -> {
                 timelineController.invokeOnCurrentTimeline {
-                    replyMessage(capturedMode.eventId, message.markdown, message.html, message.mentions)
+                    replyMessage(capturedMode.eventId, message.markdown, message.html, message.intentionalMentions)
                 }
             }
         }
@@ -623,15 +639,15 @@ class MessageComposerPresenter @Inject constructor(
                 ?.let { state ->
                     buildList {
                         if (state.hasAtRoomMention) {
-                            add(Mention.AtRoom)
+                            add(IntentionalMention.Room)
                         }
                         for (userId in state.userIds) {
-                            add(Mention.User(UserId(userId)))
+                            add(IntentionalMention.User(UserId(userId)))
                         }
                     }
                 }
                 .orEmpty()
-            Message(html = html, markdown = markdown, mentions = mentions)
+            Message(html = html, markdown = markdown, intentionalMentions = mentions)
         } else {
             val markdown = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
             val mentions = if (withMentions) {
@@ -639,7 +655,7 @@ class MessageComposerPresenter @Inject constructor(
             } else {
                 emptyList()
             }
-            Message(html = null, markdown = markdown, mentions = mentions)
+            Message(html = null, markdown = markdown, intentionalMentions = mentions)
         }
     }
 
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt
index 332a9e75f85..3698cdedbc2 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt
@@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.messagecomposer
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import io.element.android.features.messages.impl.attachments.Attachment
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MessageComposerMode
 import io.element.android.libraries.textcomposer.model.TextEditorState
 import io.element.android.wysiwyg.display.TextDisplay
@@ -35,7 +35,7 @@ data class MessageComposerState(
     val canShareLocation: Boolean,
     val canCreatePoll: Boolean,
     val attachmentsState: AttachmentsState,
-    val memberSuggestions: ImmutableList<ResolvedMentionSuggestion>,
+    val suggestions: ImmutableList<ResolvedSuggestion>,
     val resolveMentionDisplay: (String, String) -> TextDisplay,
     val eventSink: (MessageComposerEvents) -> Unit,
 )
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt
index 824f87bb8eb..b30074bb784 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt
@@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.messagecomposer
 
 import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 import io.element.android.libraries.textcomposer.aRichTextEditorState
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MessageComposerMode
 import io.element.android.libraries.textcomposer.model.TextEditorState
 import io.element.android.wysiwyg.display.TextDisplay
@@ -41,7 +41,7 @@ fun aMessageComposerState(
     canShareLocation: Boolean = true,
     canCreatePoll: Boolean = true,
     attachmentsState: AttachmentsState = AttachmentsState.None,
-    memberSuggestions: ImmutableList<ResolvedMentionSuggestion> = persistentListOf(),
+    suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
 ) = MessageComposerState(
     textEditorState = textEditorState,
     isFullScreen = isFullScreen,
@@ -51,7 +51,7 @@ fun aMessageComposerState(
     canShareLocation = canShareLocation,
     canCreatePoll = canCreatePoll,
     attachmentsState = attachmentsState,
-    memberSuggestions = memberSuggestions,
+    suggestions = suggestions,
     resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
     eventSink = {},
 )
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RoomAliasSuggestionsDataSource.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RoomAliasSuggestionsDataSource.kt
new file mode 100644
index 00000000000..860b1147cd2
--- /dev/null
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RoomAliasSuggestionsDataSource.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.messagecomposer
+
+import com.squareup.anvil.annotations.ContributesBinding
+import io.element.android.libraries.di.SessionScope
+import io.element.android.libraries.matrix.api.core.RoomAlias
+import io.element.android.libraries.matrix.api.roomlist.RoomListService
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+data class RoomAliasSuggestion(
+    val roomAlias: RoomAlias,
+    val roomSummary: RoomSummary,
+)
+
+interface RoomAliasSuggestionsDataSource {
+    fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>>
+}
+
+@ContributesBinding(SessionScope::class)
+class DefaultRoomAliasSuggestionsDataSource @Inject constructor(
+    private val roomListService: RoomListService,
+) : RoomAliasSuggestionsDataSource {
+    override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
+        return roomListService
+            .allRooms
+            .filteredSummaries
+            .map { roomSummaries ->
+                roomSummaries
+                    .mapNotNull { roomSummary ->
+                        roomSummary.canonicalAlias?.let { roomAlias ->
+                            RoomAliasSuggestion(
+                                roomAlias = roomAlias,
+                                roomSummary = roomSummary,
+                            )
+                        }
+                    }
+            }
+    }
+}
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
similarity index 64%
rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt
rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
index 48c626680ed..c52ef4771c2 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.element.android.features.messages.impl.mentions
+package io.element.android.features.messages.impl.messagecomposer.suggestions
 
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
@@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
@@ -39,38 +40,42 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
 import io.element.android.libraries.designsystem.preview.PreviewsDayNight
 import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
 import io.element.android.libraries.designsystem.theme.components.Text
+import io.element.android.libraries.matrix.api.core.RoomAlias
 import io.element.android.libraries.matrix.api.core.RoomId
 import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.room.RoomMember
 import io.element.android.libraries.matrix.api.room.RoomMembershipState
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.matrix.ui.components.aRoomSummaryDetails
+import io.element.android.libraries.matrix.ui.model.getAvatarData
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.persistentListOf
 
 @Composable
-fun MentionSuggestionsPickerView(
+fun SuggestionsPickerView(
     roomId: RoomId,
     roomName: String?,
     roomAvatarData: AvatarData?,
-    memberSuggestions: ImmutableList<ResolvedMentionSuggestion>,
-    onSelectSuggestion: (ResolvedMentionSuggestion) -> Unit,
+    suggestions: ImmutableList<ResolvedSuggestion>,
+    onSelectSuggestion: (ResolvedSuggestion) -> Unit,
     modifier: Modifier = Modifier,
 ) {
     LazyColumn(
         modifier = modifier.fillMaxWidth(),
     ) {
         items(
-            memberSuggestions,
+            suggestions,
             key = { suggestion ->
                 when (suggestion) {
-                    is ResolvedMentionSuggestion.AtRoom -> "@room"
-                    is ResolvedMentionSuggestion.Member -> suggestion.roomMember.userId.value
+                    is ResolvedSuggestion.AtRoom -> "@room"
+                    is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
+                    is ResolvedSuggestion.Alias -> suggestion.roomAlias.value
                 }
             }
         ) {
             Column(modifier = Modifier.fillParentMaxWidth()) {
-                RoomMemberSuggestionItemView(
-                    memberSuggestion = it,
+                SuggestionItemView(
+                    suggestion = it,
                     roomId = roomId.value,
                     roomName = roomName,
                     roomAvatar = roomAvatarData,
@@ -84,33 +89,33 @@ fun MentionSuggestionsPickerView(
 }
 
 @Composable
-private fun RoomMemberSuggestionItemView(
-    memberSuggestion: ResolvedMentionSuggestion,
+private fun SuggestionItemView(
+    suggestion: ResolvedSuggestion,
     roomId: String,
     roomName: String?,
     roomAvatar: AvatarData?,
-    onSelectSuggestion: (ResolvedMentionSuggestion) -> Unit,
+    onSelectSuggestion: (ResolvedSuggestion) -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    Row(modifier = modifier.clickable { onSelectSuggestion(memberSuggestion) }, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
-        val avatarData = when (memberSuggestion) {
-            is ResolvedMentionSuggestion.AtRoom -> roomAvatar?.copy(size = AvatarSize.Suggestion)
-                ?: AvatarData(roomId, roomName, null, AvatarSize.Suggestion)
-            is ResolvedMentionSuggestion.Member -> AvatarData(
-                id = memberSuggestion.roomMember.userId.value,
-                name = memberSuggestion.roomMember.displayName,
-                url = memberSuggestion.roomMember.avatarUrl,
-                size = AvatarSize.Suggestion,
-            )
+    Row(
+        modifier = modifier.clickable { onSelectSuggestion(suggestion) },
+        horizontalArrangement = Arrangement.spacedBy(16.dp),
+    ) {
+        val avatarSize = AvatarSize.Suggestion
+        val avatarData = when (suggestion) {
+            is ResolvedSuggestion.AtRoom -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize)
+            is ResolvedSuggestion.Member -> suggestion.roomMember.getAvatarData(avatarSize)
+            is ResolvedSuggestion.Alias -> suggestion.roomSummary.getAvatarData(avatarSize)
         }
-        val title = when (memberSuggestion) {
-            is ResolvedMentionSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
-            is ResolvedMentionSuggestion.Member -> memberSuggestion.roomMember.displayName
+        val title = when (suggestion) {
+            is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
+            is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
+            is ResolvedSuggestion.Alias -> suggestion.roomSummary.name
         }
-
-        val subtitle = when (memberSuggestion) {
-            is ResolvedMentionSuggestion.AtRoom -> "@room"
-            is ResolvedMentionSuggestion.Member -> memberSuggestion.roomMember.userId.value
+        val subtitle = when (suggestion) {
+            is ResolvedSuggestion.AtRoom -> "@room"
+            is ResolvedSuggestion.Member -> suggestion.roomMember.userId.value
+            is ResolvedSuggestion.Alias -> suggestion.roomAlias.value
         }
 
         Avatar(avatarData = avatarData, modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp))
@@ -142,7 +147,7 @@ private fun RoomMemberSuggestionItemView(
 
 @PreviewsDayNight
 @Composable
-internal fun MentionSuggestionsPickerViewPreview() {
+internal fun SuggestionsPickerViewPreview() {
     ElementPreview {
         val roomMember = RoomMember(
             userId = UserId("@alice:server.org"),
@@ -155,14 +160,24 @@ internal fun MentionSuggestionsPickerViewPreview() {
             isIgnored = false,
             role = RoomMember.Role.USER,
         )
-        MentionSuggestionsPickerView(
+        val anAlias = remember { RoomAlias("#room:domain.org") }
+        val roomSummaryDetails = remember {
+            aRoomSummaryDetails(
+                name = "My room",
+            )
+        }
+        SuggestionsPickerView(
             roomId = RoomId("!room:matrix.org"),
             roomName = "Room",
             roomAvatarData = null,
-            memberSuggestions = persistentListOf(
-                ResolvedMentionSuggestion.AtRoom,
-                ResolvedMentionSuggestion.Member(roomMember),
-                ResolvedMentionSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
+            suggestions = persistentListOf(
+                ResolvedSuggestion.AtRoom,
+                ResolvedSuggestion.Member(roomMember),
+                ResolvedSuggestion.Member(roomMember.copy(userId = UserId("@bob:server.org"), displayName = "Bob")),
+                ResolvedSuggestion.Alias(
+                    anAlias,
+                    roomSummaryDetails,
+                )
             ),
             onSelectSuggestion = {}
         )
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt
similarity index 62%
rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt
rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt
index f0e89c1148a..171f4a2713f 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsProcessor.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt
@@ -14,63 +14,63 @@
  * limitations under the License.
  */
 
-package io.element.android.features.messages.impl.mentions
+package io.element.android.features.messages.impl.messagecomposer.suggestions
 
+import io.element.android.features.messages.impl.messagecomposer.RoomAliasSuggestion
 import io.element.android.libraries.core.data.filterUpTo
 import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
 import io.element.android.libraries.matrix.api.room.RoomMember
 import io.element.android.libraries.matrix.api.room.RoomMembershipState
 import io.element.android.libraries.matrix.api.room.roomMembers
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.Suggestion
 import io.element.android.libraries.textcomposer.model.SuggestionType
+import javax.inject.Inject
 
 /**
- * This class is responsible for processing mention suggestions when `@`, `/` or `#` are type in the composer.
+ * This class is responsible for processing suggestions when `@`, `/` or `#` are type in the composer.
  */
-object MentionSuggestionsProcessor {
-    // We don't want to retrieve thousands of members
-    private const val MAX_BATCH_ITEMS = 100
-
+class SuggestionsProcessor @Inject constructor() {
     /**
-     *  Process the mention suggestions.
+     *  Process the suggestion.
      *  @param suggestion The current suggestion input
      *  @param roomMembersState The room members state, it contains the current users in the room
+     *  @param roomAliasSuggestions The available room alias suggestions
      *  @param currentUserId The current user id
      *  @param canSendRoomMention Should return true if the current user can send room mentions
-     *  @return The list of mentions to display
+     *  @return The list of suggestions to display
      */
     suspend fun process(
         suggestion: Suggestion?,
         roomMembersState: MatrixRoomMembersState,
+        roomAliasSuggestions: List<RoomAliasSuggestion>,
         currentUserId: UserId,
         canSendRoomMention: suspend () -> Boolean,
-    ): List<ResolvedMentionSuggestion> {
-        val members = roomMembersState.roomMembers()
-        return when {
-            members.isNullOrEmpty() || suggestion == null -> {
+    ): List<ResolvedSuggestion> {
+        suggestion ?: return emptyList()
+        return when (suggestion.type) {
+            SuggestionType.Mention -> {
+                // Replace suggestions
+                val members = roomMembersState.roomMembers()
+                val matchingMembers = getMemberSuggestions(
+                    query = suggestion.text,
+                    roomMembers = members,
+                    currentUserId = currentUserId,
+                    canSendRoomMention = canSendRoomMention()
+                )
+                matchingMembers
+            }
+            SuggestionType.Room -> {
+                roomAliasSuggestions
+                    .filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) }
+                    .map { ResolvedSuggestion.Alias(it.roomAlias, it.roomSummary) }
+            }
+            SuggestionType.Command,
+            is SuggestionType.Custom -> {
                 // Clear suggestions
                 emptyList()
             }
-            else -> {
-                when (suggestion.type) {
-                    SuggestionType.Mention -> {
-                        // Replace suggestions
-                        val matchingMembers = getMemberSuggestions(
-                            query = suggestion.text,
-                            roomMembers = members,
-                            currentUserId = currentUserId,
-                            canSendRoomMention = canSendRoomMention()
-                        )
-                        matchingMembers
-                    }
-                    else -> {
-                        // Clear suggestions
-                        emptyList()
-                    }
-                }
-            }
         }
     }
 
@@ -79,7 +79,7 @@ object MentionSuggestionsProcessor {
         roomMembers: List<RoomMember>?,
         currentUserId: UserId,
         canSendRoomMention: Boolean,
-    ): List<ResolvedMentionSuggestion> {
+    ): List<ResolvedSuggestion> {
         return if (roomMembers.isNullOrEmpty()) {
             emptyList()
         } else {
@@ -97,13 +97,18 @@ object MentionSuggestionsProcessor {
                 .filterUpTo(MAX_BATCH_ITEMS) { member ->
                     isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query)
                 }
-                .map(ResolvedMentionSuggestion::Member)
+                .map(ResolvedSuggestion::Member)
 
             if ("room".contains(query) && canSendRoomMention) {
-                listOf(ResolvedMentionSuggestion.AtRoom) + matchingMembers
+                listOf(ResolvedSuggestion.AtRoom) + matchingMembers
             } else {
                 matchingMembers
             }
         }
     }
+
+    companion object {
+        // We don't want to retrieve thousands of members
+        private const val MAX_BATCH_ITEMS = 100
+    }
 }
diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt
index c0ebb4ee16d..4fae510f00b 100644
--- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt
+++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt
@@ -100,7 +100,7 @@ class SharePresenter @AssistedInject constructor(
                             matrixClient.getRoom(roomId)?.sendMessage(
                                 body = text,
                                 htmlBody = null,
-                                mentions = emptyList(),
+                                intentionalMentions = emptyList(),
                             )?.isSuccess.orFalse()
                         }
                         .all { it }
diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt
index ccc910d02c1..93951003f8a 100644
--- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt
+++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt
@@ -72,6 +72,13 @@ enum class FeatureFlags(
         defaultValue = { true },
         isFinished = false,
     ),
+    RoomAliasSuggestions(
+        key = "feature.roomAliasSuggestions",
+        title = "Room alias suggestions",
+        description = "Type `#` to get room alias suggestions and insert them",
+        defaultValue = { false },
+        isFinished = false,
+    ),
     MarkAsUnread(
         key = "feature.markAsUnread",
         title = "Mark as unread",
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt
index 5e562f43c56..a7b2bf26cf3 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt
@@ -25,6 +25,5 @@ interface PermalinkBuilder {
 }
 
 sealed class PermalinkBuilderError : Throwable() {
-    data object InvalidUserId : PermalinkBuilderError()
-    data object InvalidRoomAlias : PermalinkBuilderError()
+    data object InvalidData : PermalinkBuilderError()
 }
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt
similarity index 71%
rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt
rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt
index a02fedde4b3..0931cf04608 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt
@@ -16,12 +16,9 @@
 
 package io.element.android.libraries.matrix.api.room
 
-import io.element.android.libraries.matrix.api.core.RoomId
 import io.element.android.libraries.matrix.api.core.UserId
 
-sealed interface Mention {
-    data class User(val userId: UserId) : Mention
-    data object AtRoom : Mention
-    data class Room(val roomId: RoomId) : Mention
-    data class RoomAlias(val roomAlias: RoomAlias?) : Mention
+sealed interface IntentionalMention {
+    data class User(val userId: UserId) : IntentionalMention
+    data object Room : IntentionalMention
 }
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index a469904aacd..b3feb25d5f8 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -129,9 +129,9 @@ interface MatrixRoom : Closeable {
 
     suspend fun userAvatarUrl(userId: UserId): Result<String?>
 
-    suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
+    suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit>
 
-    suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
+    suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit>
 
     suspend fun sendImage(
         file: File,
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt
index f42ec5fbe9a..9585b2521bd 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt
@@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
 import io.element.android.libraries.matrix.api.media.MediaUploadHandler
 import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.poll.PollKind
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.location.AssetType
 import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
 import kotlinx.coroutines.flow.Flow
@@ -52,15 +52,24 @@ interface Timeline : AutoCloseable {
     fun paginationStatus(direction: PaginationDirection): StateFlow<PaginationStatus>
     val timelineItems: Flow<List<MatrixTimelineItem>>
 
-    suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
+    suspend fun sendMessage(
+        body: String,
+        htmlBody: String?,
+        intentionalMentions: List<IntentionalMention>,
+    ): Result<Unit>
 
-    suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
+    suspend fun editMessage(
+        originalEventId: EventId?,
+        transactionId: TransactionId?,
+        body: String, htmlBody: String?,
+        intentionalMentions: List<IntentionalMention>,
+    ): Result<Unit>
 
     suspend fun replyMessage(
         eventId: EventId,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
         fromNotification: Boolean = false,
     ): Result<Unit>
 
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt
index 30a458b28f4..e3a327300f0 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt
@@ -31,7 +31,7 @@ import javax.inject.Inject
 class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder {
     override fun permalinkForUser(userId: UserId): Result<String> {
         if (!MatrixPatterns.isUserId(userId.value)) {
-            return Result.failure(PermalinkBuilderError.InvalidUserId)
+            return Result.failure(PermalinkBuilderError.InvalidData)
         }
         return runCatching {
             matrixToUserPermalink(userId.value)
@@ -40,7 +40,7 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder {
 
     override fun permalinkForRoomAlias(roomAlias: RoomAlias): Result<String> {
         if (!MatrixPatterns.isRoomAlias(roomAlias.value)) {
-            return Result.failure(PermalinkBuilderError.InvalidRoomAlias)
+            return Result.failure(PermalinkBuilderError.InvalidData)
         }
         return runCatching {
             matrixToRoomAliasPermalink(roomAlias.value)
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt
index 795ac2e003b..85c63aebc10 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt
@@ -16,11 +16,11 @@
 
 package io.element.android.libraries.matrix.impl.room
 
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import org.matrix.rustcomponents.sdk.Mentions
 
-fun List<Mention>.map(): Mentions {
-    val hasAtRoom = any { it is Mention.AtRoom }
-    val userIds = filterIsInstance<Mention.User>().map { it.userId.value }
-    return Mentions(userIds, hasAtRoom)
+fun List<IntentionalMention>.map(): Mentions {
+    val hasRoom = any { it is IntentionalMention.Room }
+    val userIds = filterIsInstance<IntentionalMention.User>().map { it.userId.value }
+    return Mentions(userIds, hasRoom)
 }
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
index 111370dcc4a..20dceeeb873 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
@@ -33,11 +33,11 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler
 import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
 import io.element.android.libraries.matrix.api.poll.PollKind
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.MatrixRoom
 import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
 import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
 import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
-import io.element.android.libraries.matrix.api.room.Mention
 import io.element.android.libraries.matrix.api.room.MessageEventType
 import io.element.android.libraries.matrix.api.room.RoomMember
 import io.element.android.libraries.matrix.api.room.StateEventType
@@ -340,16 +340,21 @@ class RustMatrixRoom(
         }
     }
 
-    override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(roomDispatcher) {
+    override suspend fun editMessage(
+        eventId: EventId,
+        body: String,
+        htmlBody: String?,
+        intentionalMentions: List<IntentionalMention>
+    ): Result<Unit> = withContext(roomDispatcher) {
         runCatching {
-            MessageEventContent.from(body, htmlBody, mentions).use { newContent ->
+            MessageEventContent.from(body, htmlBody, intentionalMentions).use { newContent ->
                 innerRoom.edit(eventId.value, newContent)
             }
         }
     }
 
-    override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> {
-        return liveTimeline.sendMessage(body, htmlBody, mentions)
+    override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit> {
+        return liveTimeline.sendMessage(body, htmlBody, intentionalMentions)
     }
 
     override suspend fun leave(): Result<Unit> = withContext(roomDispatcher) {
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
index 87fca3d3951..8c774d2d294 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt
@@ -26,8 +26,8 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
 import io.element.android.libraries.matrix.api.media.MediaUploadHandler
 import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.poll.PollKind
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.MatrixRoom
-import io.element.android.libraries.matrix.api.room.Mention
 import io.element.android.libraries.matrix.api.room.isDm
 import io.element.android.libraries.matrix.api.room.location.AssetType
 import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
@@ -263,8 +263,12 @@ class RustTimeline(
         }
     }
 
-    override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = withContext(dispatcher) {
-        MessageEventContent.from(body, htmlBody, mentions).use { content ->
+    override suspend fun sendMessage(
+        body: String,
+        htmlBody: String?,
+        intentionalMentions: List<IntentionalMention>,
+    ): Result<Unit> = withContext(dispatcher) {
+        MessageEventContent.from(body, htmlBody, intentionalMentions).use { content ->
             runCatching<Unit> {
                 inner.send(content)
             }
@@ -284,13 +288,13 @@ class RustTimeline(
         transactionId: TransactionId?,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
     ): Result<Unit> =
         withContext(dispatcher) {
             runCatching<Unit> {
                 getEventTimelineItem(originalEventId, transactionId).use { item ->
                     inner.edit(
-                        newContent = MessageEventContent.from(body, htmlBody, mentions),
+                        newContent = MessageEventContent.from(body, htmlBody, intentionalMentions),
                         item = item,
                     )
                 }
@@ -301,11 +305,11 @@ class RustTimeline(
         eventId: EventId,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
         fromNotification: Boolean,
     ): Result<Unit> = withContext(dispatcher) {
         runCatching {
-            val msg = MessageEventContent.from(body, htmlBody, mentions)
+            val msg = MessageEventContent.from(body, htmlBody, intentionalMentions)
             inner.sendReply(msg, eventId.value)
         }
     }
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
index e1728bb5282..70de27a30d1 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt
@@ -16,7 +16,7 @@
 
 package io.element.android.libraries.matrix.impl.util
 
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.impl.room.map
 import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
 import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
@@ -26,11 +26,11 @@ import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
  * Creates a [RoomMessageEventContentWithoutRelation] from a body, an html body and a list of mentions.
  */
 object MessageEventContent {
-    fun from(body: String, htmlBody: String?, mentions: List<Mention>): RoomMessageEventContentWithoutRelation {
+    fun from(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): RoomMessageEventContentWithoutRelation {
         return if (htmlBody != null) {
             messageEventContentFromHtml(body, htmlBody)
         } else {
             messageEventContentFromMarkdown(body)
-        }.withMentions(mentions.map())
+        }.withMentions(intentionalMentions.map())
     }
 }
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt
index f5c407101e4..1286f3b065c 100644
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt
@@ -171,14 +171,14 @@ class NotificationBroadcastReceiverHandler @Inject constructor(
                 eventId = threadId.asEventId(),
                 body = message,
                 htmlBody = null,
-                mentions = emptyList(),
+                intentionalMentions = emptyList(),
                 fromNotification = true,
             )
         } else {
             room.liveTimeline.sendMessage(
                 body = message,
                 htmlBody = null,
-                mentions = emptyList()
+                intentionalMentions = emptyList()
             )
         }.onFailure {
             Timber.e(it, "Failed to send smart reply message")
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
index ea31f515852..d93b1f8ab02 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt
@@ -111,13 +111,13 @@ fun MarkdownTextInput(
                         state.text.update(editable, false)
                         state.lineCount = lineCount
 
-                        state.currentMentionSuggestion = editable?.checkSuggestionNeeded()
-                        onReceiveSuggestion(state.currentMentionSuggestion)
+                        state.currentSuggestion = editable?.checkSuggestionNeeded()
+                        onReceiveSuggestion(state.currentSuggestion)
                     }
                     onSelectionChangeListener = { selStart, selEnd ->
                         state.selection = selStart..selEnd
-                        state.currentMentionSuggestion = editableText.checkSuggestionNeeded()
-                        onReceiveSuggestion(state.currentMentionSuggestion)
+                        state.currentSuggestion = editableText.checkSuggestionNeeded()
+                        onReceiveSuggestion(state.currentSuggestion)
                     }
                     if (onSelectRichContent != null) {
                         ViewCompat.setOnReceiveContentListener(
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedMentionSuggestion.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
similarity index 67%
rename from libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedMentionSuggestion.kt
rename to libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
index 03bc48f53df..7bb0fa6dea4 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedMentionSuggestion.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt
@@ -17,10 +17,13 @@
 package io.element.android.libraries.textcomposer.mentions
 
 import androidx.compose.runtime.Immutable
+import io.element.android.libraries.matrix.api.core.RoomAlias
 import io.element.android.libraries.matrix.api.room.RoomMember
+import io.element.android.libraries.matrix.api.roomlist.RoomSummary
 
 @Immutable
-sealed interface ResolvedMentionSuggestion {
-    data object AtRoom : ResolvedMentionSuggestion
-    data class Member(val roomMember: RoomMember) : ResolvedMentionSuggestion
+sealed interface ResolvedSuggestion {
+    data object AtRoom : ResolvedSuggestion
+    data class Member(val roomMember: RoomMember) : ResolvedSuggestion
+    data class Alias(val roomAlias: RoomAlias, val roomSummary: RoomSummary) : ResolvedSuggestion
 }
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt
index dd03c663666..f1437ee1f5d 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt
@@ -31,13 +31,14 @@ import androidx.compose.runtime.saveable.SaverScope
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.core.text.getSpans
+import io.element.android.libraries.matrix.api.core.RoomAlias
 import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.textcomposer.components.markdown.StableCharSequence
 import io.element.android.libraries.textcomposer.mentions.MentionSpan
 import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.mentions.getMentionSpans
 import kotlinx.parcelize.Parcelize
 
@@ -51,16 +52,16 @@ class MarkdownTextEditorState(
     var hasFocus by mutableStateOf(initialFocus)
     var requestFocusAction by mutableStateOf({})
     var lineCount by mutableIntStateOf(1)
-    var currentMentionSuggestion by mutableStateOf<Suggestion?>(null)
+    var currentSuggestion by mutableStateOf<Suggestion?>(null)
 
-    fun insertMention(
-        mention: ResolvedMentionSuggestion,
+    fun insertSuggestion(
+        resolvedSuggestion: ResolvedSuggestion,
         mentionSpanProvider: MentionSpanProvider,
         permalinkBuilder: PermalinkBuilder,
     ) {
-        val suggestion = currentMentionSuggestion ?: return
-        when (mention) {
-            is ResolvedMentionSuggestion.AtRoom -> {
+        val suggestion = currentSuggestion ?: return
+        when (resolvedSuggestion) {
+            is ResolvedSuggestion.AtRoom -> {
                 val currentText = SpannableStringBuilder(text.value())
                 val replaceText = "@room"
                 val roomPill = mentionSpanProvider.getMentionSpanFor(replaceText, "")
@@ -70,10 +71,10 @@ class MarkdownTextEditorState(
                 text.update(currentText, true)
                 selection = IntRange(end + 1, end + 1)
             }
-            is ResolvedMentionSuggestion.Member -> {
+            is ResolvedSuggestion.Member -> {
                 val currentText = SpannableStringBuilder(text.value())
-                val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value
-                val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return
+                val text = resolvedSuggestion.roomMember.displayName?.prependIndent("@") ?: resolvedSuggestion.roomMember.userId.value
+                val link = permalinkBuilder.permalinkForUser(resolvedSuggestion.roomMember.userId).getOrNull() ?: return
                 val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link)
                 currentText.replace(suggestion.start, suggestion.end, "@ ")
                 val end = suggestion.start + 1
@@ -81,6 +82,17 @@ class MarkdownTextEditorState(
                 this.text.update(currentText, true)
                 this.selection = IntRange(end + 1, end + 1)
             }
+            is ResolvedSuggestion.Alias -> {
+                val currentText = SpannableStringBuilder(text.value())
+                val text = resolvedSuggestion.roomAlias.value
+                val link = permalinkBuilder.permalinkForRoomAlias(resolvedSuggestion.roomAlias).getOrNull() ?: return
+                val mentionPill = mentionSpanProvider.getMentionSpanFor(text, link)
+                currentText.replace(suggestion.start, suggestion.end, "# ")
+                val end = suggestion.start + 1
+                currentText.setSpan(mentionPill, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+                this.text.update(currentText, true)
+                this.selection = IntRange(end + 1, end + 1)
+            }
         }
     }
 
@@ -96,14 +108,18 @@ class MarkdownTextEditorState(
                         val end = charSequence.getSpanEnd(mention)
                         when (mention.type) {
                             MentionSpan.Type.USER -> {
-                                val link = permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull() ?: continue
-                                replace(start, end, "[${mention.rawValue}]($link)")
+                                permalinkBuilder.permalinkForUser(UserId(mention.rawValue)).getOrNull()?.let { link ->
+                                    replace(start, end, "[${mention.rawValue}]($link)")
+                                }
                             }
                             MentionSpan.Type.EVERYONE -> {
                                 replace(start, end, "@room")
                             }
-                            // Nothing to do here yet
-                            MentionSpan.Type.ROOM -> Unit
+                            MentionSpan.Type.ROOM -> {
+                                permalinkBuilder.permalinkForRoomAlias(RoomAlias(mention.rawValue)).getOrNull()?.let { link ->
+                                    replace(start, end, "[${mention.text}]($link)")
+                                }
+                            }
                         }
                     }
                 }
@@ -113,13 +129,13 @@ class MarkdownTextEditorState(
         }
     }
 
-    fun getMentions(): List<Mention> {
+    fun getMentions(): List<IntentionalMention> {
         val text = SpannableString(text.value())
         val mentionSpans = text.getSpans<MentionSpan>(0, text.length)
         return mentionSpans.mapNotNull { mentionSpan ->
             when (mentionSpan.type) {
-                MentionSpan.Type.USER -> Mention.User(UserId(mentionSpan.rawValue))
-                MentionSpan.Type.EVERYONE -> Mention.AtRoom
+                MentionSpan.Type.USER -> IntentionalMention.User(UserId(mentionSpan.rawValue))
+                MentionSpan.Type.EVERYONE -> IntentionalMention.Room
                 MentionSpan.Type.ROOM -> null
             }
         }
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Message.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Message.kt
index 9467cca627a..a43f17ed64d 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Message.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Message.kt
@@ -16,10 +16,10 @@
 
 package io.element.android.libraries.textcomposer.model
 
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 
 data class Message(
     val html: String?,
     val markdown: String,
-    val mentions: List<Mention>,
+    val intentionalMentions: List<IntentionalMention>,
 )


Test patch:
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
index a6c123971ba..c9700a60bd6 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt
@@ -29,9 +29,11 @@ import io.element.android.features.messages.impl.draft.FakeComposerDraftService
 import io.element.android.features.messages.impl.fixtures.aMessageEvent
 import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory
 import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
+import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
 import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
+import io.element.android.features.messages.impl.messagecomposer.TestRichTextEditorStateFactory
+import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
 import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
-import io.element.android.features.messages.impl.textcomposer.TestRichTextEditorStateFactory
 import io.element.android.features.messages.impl.timeline.TimelineController
 import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
 import io.element.android.features.messages.impl.timeline.TimelinePresenter
@@ -1008,6 +1010,7 @@ class MessagesPresenterTest {
             analyticsService = analyticsService,
             messageComposerContext = DefaultMessageComposerContext(),
             richTextEditorStateFactory = TestRichTextEditorStateFactory(),
+            roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
             permissionsPresenterFactory = permissionsPresenterFactory,
             permalinkParser = FakePermalinkParser(),
             permalinkBuilder = FakePermalinkBuilder(),
@@ -1016,6 +1019,7 @@ class MessagesPresenterTest {
             mentionSpanProvider = mentionSpanProvider,
             pillificationHelper = FakeTextPillificationHelper(),
             roomMemberProfilesCache = RoomMemberProfilesCache(),
+            suggestionsProcessor = SuggestionsProcessor(),
         ).apply {
             showTextFormatting = true
             isTesting = true
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt
new file mode 100644
index 00000000000..4fa976fbb4c
--- /dev/null
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.messagecomposer
+
+import app.cash.turbine.test
+import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
+import io.element.android.libraries.matrix.test.A_ROOM_ID_2
+import io.element.android.libraries.matrix.test.room.aRoomSummary
+import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class DefaultRoomAliasSuggestionsDataSourceTest {
+    @Test
+    fun `getAllRoomAliasSuggestions must emit a list of room alias suggestions`() = runTest {
+        val roomListService = FakeRoomListService()
+        val sut = DefaultRoomAliasSuggestionsDataSource(
+            roomListService
+        )
+        val aRoomSummaryWithAnAlias = aRoomSummary(
+            canonicalAlias = A_ROOM_ALIAS
+        )
+        sut.getAllRoomAliasSuggestions().test {
+            assertThat(awaitItem()).isEmpty()
+            roomListService.postAllRooms(
+                listOf(
+                    aRoomSummary(roomId = A_ROOM_ID_2, canonicalAlias = null),
+                    aRoomSummaryWithAnAlias,
+                )
+            )
+            assertThat(awaitItem()).isEqualTo(
+                listOf(
+                    RoomAliasSuggestion(
+                        roomAlias = A_ROOM_ALIAS,
+                        roomSummary = aRoomSummaryWithAnAlias
+                    )
+                )
+            )
+        }
+    }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt
new file mode 100644
index 00000000000..b7ee6f27cae
--- /dev/null
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.messagecomposer
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeRoomAliasSuggestionsDataSource(
+    initialData: List<RoomAliasSuggestion> = emptyList()
+) : RoomAliasSuggestionsDataSource {
+    private val roomAliasSuggestions = MutableStateFlow(initialData)
+
+    override fun getAllRoomAliasSuggestions(): Flow<List<RoomAliasSuggestion>> {
+        return roomAliasSuggestions
+    }
+
+    fun emitRoomAliasSuggestions(newData: List<RoomAliasSuggestion>) {
+        roomAliasSuggestions.value = newData
+    }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt
similarity index 96%
rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt
index 924616de7a6..632f5296324 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt
@@ -16,7 +16,7 @@
 
 @file:OptIn(ExperimentalCoroutinesApi::class)
 
-package io.element.android.features.messages.impl.textcomposer
+package io.element.android.features.messages.impl.messagecomposer
 
 import android.net.Uri
 import androidx.compose.runtime.remember
@@ -29,11 +29,7 @@ import im.vector.app.features.analytics.plan.Composer
 import im.vector.app.features.analytics.plan.Interaction
 import io.element.android.features.messages.impl.draft.ComposerDraftService
 import io.element.android.features.messages.impl.draft.FakeComposerDraftService
-import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
-import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
-import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
-import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
-import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
+import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
 import io.element.android.features.messages.impl.timeline.TimelineController
 import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
 import io.element.android.features.messages.impl.utils.TextPillificationHelper
@@ -50,9 +46,9 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
 import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
 import io.element.android.libraries.matrix.api.permalink.PermalinkParser
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.MatrixRoom
 import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
-import io.element.android.libraries.matrix.api.room.Mention
 import io.element.android.libraries.matrix.api.room.RoomMembershipState
 import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
 import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
@@ -90,7 +86,7 @@ import io.element.android.libraries.permissions.test.FakePermissionsPresenterFac
 import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
 import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
 import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MessageComposerMode
 import io.element.android.libraries.textcomposer.model.Suggestion
 import io.element.android.libraries.textcomposer.model.SuggestionType
@@ -368,7 +364,7 @@ class MessageComposerPresenterTest {
 
     @Test
     fun `present - edit sent message`() = runTest {
-        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
+        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val timeline = FakeTimeline().apply {
@@ -420,13 +416,13 @@ class MessageComposerPresenterTest {
 
     @Test
     fun `present - edit sent message event not found`() = runTest {
-        val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
+        val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
             Result.failure<Unit>(TimelineException.EventNotFound)
         }
         val timeline = FakeTimeline().apply {
             this.editMessageLambda = timelineEditMessageLambda
         }
-        val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<Mention> ->
+        val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val fakeMatrixRoom = FakeMatrixRoom(
@@ -480,7 +476,7 @@ class MessageComposerPresenterTest {
 
     @Test
     fun `present - edit not sent message`() = runTest {
-        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
+        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val timeline = FakeTimeline().apply {
@@ -532,7 +528,7 @@ class MessageComposerPresenterTest {
 
     @Test
     fun `present - reply message`() = runTest {
-        val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<Mention>, _: Boolean ->
+        val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
             Result.success(Unit)
         }
         val timeline = FakeTimeline().apply {
@@ -974,34 +970,34 @@ class MessageComposerPresenterTest {
 
             // A null suggestion (no suggestion was received) returns nothing
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(null))
-            assertThat(awaitItem().memberSuggestions).isEmpty()
+            assertThat(awaitItem().suggestions).isEmpty()
 
             // An empty suggestion returns the room and joined members that are not the current user
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
-            assertThat(awaitItem().memberSuggestions)
-                .containsExactly(ResolvedMentionSuggestion.AtRoom, ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
+            assertThat(awaitItem().suggestions)
+                .containsExactly(ResolvedSuggestion.AtRoom, ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david))
 
             // A suggestion containing a part of "room" will also return the room mention
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo")))
-            assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.AtRoom)
+            assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.AtRoom)
 
             // A non-empty suggestion will return those joined members whose user id matches it
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob")))
-            assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.Member(bob))
+            assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(bob))
 
             // A non-empty suggestion will return those joined members whose display name matches it
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave")))
-            assertThat(awaitItem().memberSuggestions).containsExactly(ResolvedMentionSuggestion.Member(david))
+            assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(david))
 
             // If the suggestion isn't a mention, no suggestions are returned
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, "")))
-            assertThat(awaitItem().memberSuggestions).isEmpty()
+            assertThat(awaitItem().suggestions).isEmpty()
 
             // If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned
             canUserTriggerRoomNotificationResult = false
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
-            assertThat(awaitItem().memberSuggestions)
-                .containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
+            assertThat(awaitItem().suggestions)
+                .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david))
         }
     }
 
@@ -1039,13 +1035,12 @@ class MessageComposerPresenterTest {
             // An empty suggestion returns the joined members that are not the current user, but not the room
             initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
             skipItems(1)
-            assertThat(awaitItem().memberSuggestions)
-                .containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
+            assertThat(awaitItem().suggestions)
+                .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david))
         }
     }
 
-    @Test
-    fun `present - insertMention for user in rich text editor`() = runTest {
+    fun `present - InsertSuggestion`() = runTest {
         val presenter = createPresenter(
             coroutineScope = this,
             permalinkBuilder = FakePermalinkBuilder(
@@ -1059,7 +1054,7 @@ class MessageComposerPresenterTest {
         }.test {
             val initialState = awaitFirstItem()
             initialState.textEditorState.setHtml("Hey @bo")
-            initialState.eventSink(MessageComposerEvents.InsertMention(ResolvedMentionSuggestion.Member(aRoomMember(userId = A_USER_ID_2))))
+            initialState.eventSink(MessageComposerEvents.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2))))
 
             assertThat(initialState.textEditorState.messageHtml())
                 .isEqualTo("Hey <a href='https://matrix.to/#/${A_USER_ID_2.value}'>${A_USER_ID_2.value}</a>")
@@ -1069,17 +1064,17 @@ class MessageComposerPresenterTest {
     @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun `present - send messages with intentional mentions`() = runTest {
-        val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<Mention>, _: Boolean ->
+        val replyMessageLambda = lambdaRecorder { _: EventId, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
             Result.success(Unit)
         }
-        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
+        val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val timeline = FakeTimeline().apply {
             this.replyMessageLambda = replyMessageLambda
             this.editMessageLambda = editMessageLambda
         }
-        val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<Mention> ->
+        val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val room = FakeMatrixRoom(
@@ -1107,7 +1102,7 @@ class MessageComposerPresenterTest {
             advanceUntilIdle()
 
             sendMessageResult.assertions().isCalledOnce()
-                .with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID))))
+                .with(value(A_MESSAGE), any(), value(listOf(IntentionalMention.User(A_USER_ID))))
 
             // Check intentional mentions on reply sent
             initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
@@ -1124,7 +1119,7 @@ class MessageComposerPresenterTest {
 
             assert(replyMessageLambda)
                 .isCalledOnce()
-                .with(any(), any(), any(), value(listOf(Mention.User(A_USER_ID_2))), value(false))
+                .with(any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_2))), value(false))
 
             // Check intentional mentions on edit message
             skipItems(1)
@@ -1142,7 +1137,7 @@ class MessageComposerPresenterTest {
 
             assert(editMessageLambda)
                 .isCalledOnce()
-                .with(any(), any(), any(), any(), value(listOf(Mention.User(A_USER_ID_3))))
+                .with(any(), any(), any(), any(), value(listOf(IntentionalMention.User(A_USER_ID_3))))
 
             skipItems(1)
         }
@@ -1507,6 +1502,7 @@ class MessageComposerPresenterTest {
         analyticsService,
         DefaultMessageComposerContext(),
         TestRichTextEditorStateFactory(),
+        roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
         permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
         permalinkParser = permalinkParser,
         permalinkBuilder = permalinkBuilder,
@@ -1515,6 +1511,7 @@ class MessageComposerPresenterTest {
         mentionSpanProvider = mentionSpanProvider,
         pillificationHelper = textPillificationHelper,
         roomMemberProfilesCache = roomMemberProfilesCache,
+        suggestionsProcessor = SuggestionsProcessor(),
     ).apply {
         isTesting = true
         showTextFormatting = isRichTextEditorEnabled
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/TestRichTextEditorStateFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt
similarity index 86%
rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/TestRichTextEditorStateFactory.kt
rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt
index 921a7331fdc..df5c1c61831 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/TestRichTextEditorStateFactory.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package io.element.android.features.messages.impl.textcomposer
+package io.element.android.features.messages.impl.messagecomposer
 
 import androidx.compose.runtime.Composable
-import io.element.android.features.messages.impl.messagecomposer.RichTextEditorStateFactory
 import io.element.android.wysiwyg.compose.RichTextEditorState
 import io.element.android.wysiwyg.compose.rememberRichTextEditorState
 
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt
new file mode 100644
index 00000000000..c3f7806cc44
--- /dev/null
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2024 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.element.android.features.messages.impl.messagecomposer.suggestions
+
+import com.google.common.truth.Truth.assertThat
+import io.element.android.features.messages.impl.messagecomposer.RoomAliasSuggestion
+import io.element.android.libraries.matrix.api.core.UserId
+import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
+import io.element.android.libraries.matrix.api.room.RoomMembershipState
+import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
+import io.element.android.libraries.matrix.test.A_USER_ID
+import io.element.android.libraries.matrix.test.A_USER_ID_2
+import io.element.android.libraries.matrix.test.room.aRoomMember
+import io.element.android.libraries.matrix.test.room.aRoomSummary
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
+import io.element.android.libraries.textcomposer.model.Suggestion
+import io.element.android.libraries.textcomposer.model.SuggestionType
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class SuggestionsProcessorTest {
+    private fun aMentionSuggestion(text: String) = Suggestion(0, 1, SuggestionType.Mention, text)
+    private fun aRoomSuggestion(text: String) = Suggestion(0, 1, SuggestionType.Room, text)
+    private val aCommandSuggestion = Suggestion(0, 1, SuggestionType.Command, "")
+    private val aCustomSuggestion = Suggestion(0, 1, SuggestionType.Custom("*"), "")
+
+    private val suggestionsProcessor = SuggestionsProcessor()
+
+    @Test
+    fun `processing null suggestion will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = null,
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember())),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Command will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = aCommandSuggestion,
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember())),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Custom will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = aCustomSuggestion,
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember())),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with not loaded members will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion(""),
+            roomMembersState = MatrixRoomMembersState.Unknown,
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with no members will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion(""),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Room suggestion with no aliases will return empty suggestion`() = runTest {
+        val result = suggestionsProcessor.process(
+            suggestion = aRoomSuggestion(""),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Room suggestion with aliases ignoring cases will return a suggestion`() = runTest {
+        val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
+        val result = suggestionsProcessor.process(
+            suggestion = aRoomSuggestion("ALI"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
+            roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary)
+            )
+        )
+    }
+
+    @Test
+    fun `processing Room suggestion with aliases will return a suggestion`() = runTest {
+        val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
+        val result = suggestionsProcessor.process(
+            suggestion = aRoomSuggestion("ali"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
+            roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary)
+            )
+        )
+    }
+
+    @Test
+    fun `processing Room suggestion with aliases not found will return no suggestions`() = runTest {
+        val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
+        val result = suggestionsProcessor.process(
+            suggestion = aRoomSuggestion("tot"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
+            roomAliasSuggestions = listOf(RoomAliasSuggestion(A_ROOM_ALIAS, aRoomSummary)),
+            currentUserId = A_USER_ID,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with return matching matrix Id`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = null)
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("ali"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.Member(aRoomMember)
+            )
+        )
+    }
+
+    @Test
+    fun `processing Mention suggestion with not return the current user`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = null)
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("ali"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = UserId("@alice:server.org"),
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with return empty list if there is no matches`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "alice")
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("bo"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with not return not joined member`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), membership = RoomMembershipState.INVITE)
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("ali"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEmpty()
+    }
+
+    @Test
+    fun `processing Mention suggestion with return matching display name`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "bob")
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("bo"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.Member(aRoomMember)
+            )
+        )
+    }
+
+    @Test
+    fun `processing Mention suggestion with return matching display name and room if allowed`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "ro")
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("ro"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { true },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.AtRoom,
+                ResolvedSuggestion.Member(aRoomMember),
+            )
+        )
+    }
+
+    @Test
+    fun `processing Mention suggestion with return matching display name but not room if not allowed`() = runTest {
+        val aRoomMember = aRoomMember(userId = UserId("@alice:server.org"), displayName = "ro")
+        val result = suggestionsProcessor.process(
+            suggestion = aMentionSuggestion("ro"),
+            roomMembersState = MatrixRoomMembersState.Ready(persistentListOf(aRoomMember)),
+            roomAliasSuggestions = emptyList(),
+            currentUserId = A_USER_ID_2,
+            canSendRoomMention = { false },
+        )
+        assertThat(result).isEqualTo(
+            listOf(
+                ResolvedSuggestion.Member(aRoomMember),
+            )
+        )
+    }
+}
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
index 887ce88d95a..a1b0e73b655 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt
@@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.timeline
 
 import app.cash.turbine.test
 import com.google.common.truth.Truth.assertThat
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
 import io.element.android.libraries.matrix.api.timeline.Timeline
 import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -162,10 +162,10 @@ class TimelineControllerTest {
 
     @Test
     fun `test invokeOnCurrentTimeline use the detached timeline and not the live timeline`() = runTest {
-        val lambdaForDetached = lambdaRecorder { _: String, _: String?, _: List<Mention> ->
+        val lambdaForDetached = lambdaRecorder { _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
-        val lambdaForLive = lambdaRecorder(ensureNeverCalled = true) { _: String, _: String?, _: List<Mention> ->
+        val lambdaForLive = lambdaRecorder(ensureNeverCalled = true) { _: String, _: String?, _: List<IntentionalMention> ->
             Result.success(Unit)
         }
         val liveTimeline = FakeTimeline(name = "live").apply {
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt
index 3510a362ec0..5b18c3b1757 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt
@@ -19,10 +19,11 @@ package io.element.android.libraries.matrix.test.permalink
 import io.element.android.libraries.matrix.api.core.RoomAlias
 import io.element.android.libraries.matrix.api.core.UserId
 import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
+import io.element.android.tests.testutils.lambda.lambdaError
 
 class FakePermalinkBuilder(
-    private val permalinkForUserLambda: (UserId) -> Result<String> = { Result.failure(Exception("Not implemented")) },
-    private val permalinkForRoomAliasLambda: (RoomAlias) -> Result<String> = { Result.failure(Exception("Not implemented")) },
+    private val permalinkForUserLambda: (UserId) -> Result<String> = { lambdaError() },
+    private val permalinkForRoomAliasLambda: (RoomAlias) -> Result<String> = { lambdaError() },
 ) : PermalinkBuilder {
     override fun permalinkForUser(userId: UserId): Result<String> {
         return permalinkForUserLambda(userId)
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
index 1e7eb8c0039..af1ad33bdcf 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
@@ -31,11 +31,11 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
 import io.element.android.libraries.matrix.api.poll.PollKind
 import io.element.android.libraries.matrix.api.room.CurrentUserMembership
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.MatrixRoom
 import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
 import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
 import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
-import io.element.android.libraries.matrix.api.room.Mention
 import io.element.android.libraries.matrix.api.room.MessageEventType
 import io.element.android.libraries.matrix.api.room.RoomMember
 import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@@ -105,8 +105,8 @@ class FakeMatrixRoom(
     private val setTopicResult: (String) -> Result<Unit> = { lambdaError() },
     private val updateAvatarResult: (String, ByteArray) -> Result<Unit> = { _, _ -> lambdaError() },
     private val removeAvatarResult: () -> Result<Unit> = { lambdaError() },
-    private val editMessageLambda: (EventId, String, String?, List<Mention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
-    private val sendMessageResult: (String, String?, List<Mention>) -> Result<Unit> = { _, _, _ -> lambdaError() },
+    private val editMessageLambda: (EventId, String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
+    private val sendMessageResult: (String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _ -> lambdaError() },
     private val updateUserRoleResult: () -> Result<Unit> = { lambdaError() },
     private val toggleReactionResult: (String, EventId) -> Result<Unit> = { _, _ -> lambdaError() },
     private val retrySendMessageResult: (TransactionId) -> Result<Unit> = { lambdaError() },
@@ -222,12 +222,12 @@ class FakeMatrixRoom(
         return updateUserRoleResult()
     }
 
-    override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>) = simulateLongTask {
-        editMessageLambda(eventId, body, htmlBody, mentions)
+    override suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>) = simulateLongTask {
+        editMessageLambda(eventId, body, htmlBody, intentionalMentions)
     }
 
-    override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>) = simulateLongTask {
-        sendMessageResult(body, htmlBody, mentions)
+    override suspend fun sendMessage(body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>) = simulateLongTask {
+        sendMessageResult(body, htmlBody, intentionalMentions)
     }
 
     override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt
index ea9b353a670..ea286938725 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt
@@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
 import io.element.android.libraries.matrix.api.media.MediaUploadHandler
 import io.element.android.libraries.matrix.api.media.VideoInfo
 import io.element.android.libraries.matrix.api.poll.PollKind
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.room.location.AssetType
 import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
 import io.element.android.libraries.matrix.api.timeline.ReceiptType
@@ -60,7 +60,7 @@ class FakeTimeline(
     var sendMessageLambda: (
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
     ) -> Result<Unit> = { _, _, _ ->
         Result.success(Unit)
     }
@@ -68,8 +68,8 @@ class FakeTimeline(
     override suspend fun sendMessage(
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
-    ): Result<Unit> = sendMessageLambda(body, htmlBody, mentions)
+        intentionalMentions: List<IntentionalMention>,
+    ): Result<Unit> = sendMessageLambda(body, htmlBody, intentionalMentions)
 
     var redactEventLambda: (eventId: EventId?, transactionId: TransactionId?, reason: String?) -> Result<Boolean> = { _, _, _ ->
         Result.success(true)
@@ -86,7 +86,7 @@ class FakeTimeline(
         transactionId: TransactionId?,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
     ) -> Result<Unit> = { _, _, _, _, _ ->
         Result.success(Unit)
     }
@@ -96,20 +96,20 @@ class FakeTimeline(
         transactionId: TransactionId?,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
     ): Result<Unit> = editMessageLambda(
         originalEventId,
         transactionId,
         body,
         htmlBody,
-        mentions
+        intentionalMentions
     )
 
     var replyMessageLambda: (
         eventId: EventId,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
         fromNotification: Boolean,
     ) -> Result<Unit> = { _, _, _, _, _ ->
         Result.success(Unit)
@@ -119,13 +119,13 @@ class FakeTimeline(
         eventId: EventId,
         body: String,
         htmlBody: String?,
-        mentions: List<Mention>,
+        intentionalMentions: List<IntentionalMention>,
         fromNotification: Boolean,
     ): Result<Unit> = replyMessageLambda(
         eventId,
         body,
         htmlBody,
-        mentions,
+        intentionalMentions,
         fromNotification,
     )
 
diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt
index 40d32878dfe..1bddedc2e51 100644
--- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt
+++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt
@@ -24,7 +24,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
 import io.element.android.libraries.matrix.api.core.SessionId
 import io.element.android.libraries.matrix.api.core.ThreadId
 import io.element.android.libraries.matrix.api.core.asEventId
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
 import io.element.android.libraries.matrix.api.roomlist.RoomSummary
 import io.element.android.libraries.matrix.api.timeline.ReceiptType
 import io.element.android.libraries.matrix.test.AN_EVENT_ID
@@ -337,8 +337,8 @@ class NotificationBroadcastReceiverHandlerTest {
 
     @Test
     fun `Test send reply`() = runTest {
-        val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
-        val replyMessage = lambdaRecorder<EventId, String, String?, List<Mention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) }
+        val sendMessage = lambdaRecorder<String, String?, List<IntentionalMention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
+        val replyMessage = lambdaRecorder<EventId, String, String?, List<IntentionalMention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) }
         val liveTimeline = FakeTimeline().apply {
             sendMessageLambda = sendMessage
             replyMessageLambda = replyMessage
@@ -363,7 +363,7 @@ class NotificationBroadcastReceiverHandlerTest {
         runCurrent()
         sendMessage.assertions()
             .isCalledOnce()
-            .with(value(A_MESSAGE), value(null), value(emptyList<Mention>()))
+            .with(value(A_MESSAGE), value(null), value(emptyList<IntentionalMention>()))
         onNotifiableEventReceivedResult.assertions()
             .isCalledOnce()
         replyMessage.assertions()
@@ -372,7 +372,7 @@ class NotificationBroadcastReceiverHandlerTest {
 
     @Test
     fun `Test send reply blank message`() = runTest {
-        val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
+        val sendMessage = lambdaRecorder<String, String?, List<IntentionalMention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
         val liveTimeline = FakeTimeline().apply {
             sendMessageLambda = sendMessage
         }
@@ -396,8 +396,8 @@ class NotificationBroadcastReceiverHandlerTest {
 
     @Test
     fun `Test send reply to thread`() = runTest {
-        val sendMessage = lambdaRecorder<String, String?, List<Mention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
-        val replyMessage = lambdaRecorder<EventId, String, String?, List<Mention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) }
+        val sendMessage = lambdaRecorder<String, String?, List<IntentionalMention>, Result<Unit>> { _, _, _ -> Result.success(Unit) }
+        val replyMessage = lambdaRecorder<EventId, String, String?, List<IntentionalMention>, Boolean, Result<Unit>> { _, _, _, _, _ -> Result.success(Unit) }
         val liveTimeline = FakeTimeline().apply {
             sendMessageLambda = sendMessage
             replyMessageLambda = replyMessage
@@ -427,7 +427,7 @@ class NotificationBroadcastReceiverHandlerTest {
             .isCalledOnce()
         replyMessage.assertions()
             .isCalledOnce()
-            .with(value(A_THREAD_ID.asEventId()), value(A_MESSAGE), value(null), value(emptyList<Mention>()), value(true))
+            .with(value(A_THREAD_ID.asEventId()), value(A_MESSAGE), value(null), value(emptyList<IntentionalMention>()), value(true))
     }
 
     private fun createIntent(
diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt
index c5949a3a412..e0e83d16dbd 100644
--- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt
+++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt
@@ -34,7 +34,7 @@ import io.element.android.libraries.textcomposer.components.markdown.MarkdownTex
 import io.element.android.libraries.textcomposer.components.markdown.aMarkdownTextEditorState
 import io.element.android.libraries.textcomposer.mentions.MentionSpan
 import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
 import io.element.android.libraries.textcomposer.model.Suggestion
 import io.element.android.libraries.textcomposer.model.SuggestionType
@@ -157,13 +157,13 @@ class MarkdownTextInputTest {
         val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) })
         val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$A_SESSION_ID") })
         val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true)
-        state.currentMentionSuggestion = Suggestion(0, 1, SuggestionType.Mention, "")
+        state.currentSuggestion = Suggestion(0, 1, SuggestionType.Mention, "")
         rule.setMarkdownTextInput(state = state)
         var editor: EditText? = null
         rule.activityRule.scenario.onActivity {
             editor = it.findEditor()
-            state.insertMention(
-                ResolvedMentionSuggestion.Member(roomMember = aRoomMember()),
+            state.insertSuggestion(
+                ResolvedSuggestion.Member(roomMember = aRoomMember()),
                 MentionSpanProvider(permalinkParser = permalinkParser),
                 permalinkBuilder,
             )
diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt
similarity index 98%
rename from libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt
rename to libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt
index b6522305afc..19490d568b1 100644
--- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt
+++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt
@@ -32,7 +32,7 @@ import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 
 @RunWith(RobolectricTestRunner::class)
-class MentionSpanProviderTest {
+class IntentionalMentionSpanProviderTest {
     @JvmField @Rule
     val warmUpRule = WarmUpRule()
 
diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt
index b4f3c143bd7..3cd714f9503 100644
--- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt
+++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt
@@ -21,14 +21,17 @@ import androidx.core.text.buildSpannedString
 import androidx.core.text.inSpans
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
 import io.element.android.libraries.matrix.api.permalink.PermalinkData
-import io.element.android.libraries.matrix.api.room.Mention
+import io.element.android.libraries.matrix.api.room.IntentionalMention
+import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
 import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
 import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
 import io.element.android.libraries.matrix.test.room.aRoomMember
+import io.element.android.libraries.matrix.test.room.aRoomSummary
 import io.element.android.libraries.textcomposer.mentions.MentionSpan
 import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
-import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
+import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
 import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
 import io.element.android.libraries.textcomposer.model.Suggestion
 import io.element.android.libraries.textcomposer.model.SuggestionType
@@ -38,68 +41,102 @@ import org.junit.runner.RunWith
 @RunWith(AndroidJUnit4::class)
 class MarkdownTextEditorStateTest {
     @Test
-    fun `insertMention - with no currentMentionSuggestion does nothing`() {
+    fun `insertMention - room alias - getMentions return empty list`() {
+        val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
+        val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
+        val permalinkBuilder = FakePermalinkBuilder()
+        val mentionSpanProvider = aMentionSpanProvider()
+        state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
+        assertThat(state.getMentions()).isEmpty()
+    }
+
+    @Test
+    fun `insertSuggestion - room alias - with member but failed PermalinkBuilder result`() {
+        val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
+            currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
+        }
+        val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
+        val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
+        val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.failure(IllegalStateException("Failed")) })
+        val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
+        state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
+    }
+
+    @Test
+    fun `insertSuggestion - room alias`() {
+        val state = MarkdownTextEditorState(initialText = "Hello #", initialFocus = true).apply {
+            currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Room, text = "")
+        }
+        val suggestion = ResolvedSuggestion.Alias(A_ROOM_ALIAS, aRoomSummary(canonicalAlias = A_ROOM_ALIAS))
+        val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
+        val permalinkBuilder = FakePermalinkBuilder(permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/${A_ROOM_ALIAS.value}") })
+        val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
+        state.insertSuggestion(suggestion, mentionSpanProvider, permalinkBuilder)
+    }
+
+    @Test
+    fun `insertSuggestion - with no currentMentionSuggestion does nothing`() {
         val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
         val member = aRoomMember()
-        val mention = ResolvedMentionSuggestion.Member(member)
+        val mention = ResolvedSuggestion.Member(member)
         val permalinkBuilder = FakePermalinkBuilder()
         val mentionSpanProvider = aMentionSpanProvider()
 
-        state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
+        state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
 
         assertThat(state.getMentions()).isEmpty()
     }
 
     @Test
-    fun `insertMention - with member but failed PermalinkBuilder result`() {
+    fun `insertSuggestion - with member but failed PermalinkBuilder result`() {
         val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
-            currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
+            currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
         }
         val member = aRoomMember()
-        val mention = ResolvedMentionSuggestion.Member(member)
+        val mention = ResolvedSuggestion.Member(member)
         val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) })
         val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.failure(IllegalStateException("Failed")) })
         val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
 
-        state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
+        state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
 
         val mentions = state.getMentions()
         assertThat(mentions).isEmpty()
     }
 
     @Test
-    fun `insertMention - with member`() {
+    fun `insertSuggestion - with member`() {
         val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
-            currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
+            currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
         }
         val member = aRoomMember()
-        val mention = ResolvedMentionSuggestion.Member(member)
+        val mention = ResolvedSuggestion.Member(member)
         val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(member.userId) })
         val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/${member.userId}") })
         val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
 
-        state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
+        state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
 
         val mentions = state.getMentions()
         assertThat(mentions).isNotEmpty()
-        assertThat((mentions.firstOrNull() as? Mention.User)?.userId).isEqualTo(member.userId)
+        assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId).isEqualTo(member.userId)
     }
 
     @Test
-    fun `insertMention - with @room`() {
+    fun `insertSuggestion - with @room`() {
         val state = MarkdownTextEditorState(initialText = "Hello @", initialFocus = true).apply {
-            currentMentionSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
+            currentSuggestion = Suggestion(start = 6, end = 7, type = SuggestionType.Mention, text = "")
         }
-        val mention = ResolvedMentionSuggestion.AtRoom
+        val mention = ResolvedSuggestion.AtRoom
         val permalinkBuilder = FakePermalinkBuilder()
         val permalinkParser = FakePermalinkParser(result = { PermalinkData.FallbackLink(Uri.EMPTY, false) })
         val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
 
-        state.insertMention(mention, mentionSpanProvider, permalinkBuilder)
+        state.insertSuggestion(mention, mentionSpanProvider, permalinkBuilder)
 
         val mentions = state.getMentions()
         assertThat(mentions).isNotEmpty()
-        assertThat(mentions.firstOrNull()).isInstanceOf(Mention.AtRoom::class.java)
+        assertThat(mentions.firstOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
     }
 
     @Test
@@ -115,14 +152,18 @@ class MarkdownTextEditorStateTest {
     @Test
     fun `getMessageMarkdown - when there are MentionSpans returns the same text with links to the mentions`() {
         val text = "No mentions here"
-        val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") })
+        val permalinkBuilder = FakePermalinkBuilder(
+            permalinkForUserLambda = { Result.success("https://matrix.to/#/$it") },
+            permalinkForRoomAliasLambda = { Result.success("https://matrix.to/#/$it") },
+        )
         val state = MarkdownTextEditorState(initialText = text, initialFocus = true)
         state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
 
         val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
 
         assertThat(markdown).isEqualTo(
-            "Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room"
+            "Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room" +
+                " and a room [#room:domain.org](https://matrix.to/#/#room:domain.org)"
         )
     }
 
@@ -141,8 +182,8 @@ class MarkdownTextEditorStateTest {
         val mentions = state.getMentions()
 
         assertThat(mentions).isNotEmpty()
-        assertThat((mentions.firstOrNull() as? Mention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
-        assertThat(mentions.lastOrNull()).isInstanceOf(Mention.AtRoom::class.java)
+        assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
+        assertThat(mentions.lastOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
     }
 
     private fun aMentionSpanProvider(
@@ -154,6 +195,7 @@ class MarkdownTextEditorStateTest {
     private fun aMarkdownTextWithMentions(): CharSequence {
         val userMentionSpan = MentionSpan("@Alice", "@alice:matrix.org", MentionSpan.Type.USER)
         val atRoomMentionSpan = MentionSpan("@room", "@room", MentionSpan.Type.EVERYONE)
+        val roomMentionSpan = MentionSpan("#room:domain.org", "#room:domain.org", MentionSpan.Type.ROOM)
         return buildSpannedString {
             append("Hello ")
             inSpans(userMentionSpan) {
@@ -163,6 +205,10 @@ class MarkdownTextEditorStateTest {
             inSpans(atRoomMentionSpan) {
                 append("@")
             }
+            append(" and a room ")
+            inSpans(roomMentionSpan) {
+                append("#room:domain.org")
+            }
         }
     }
 }
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en.png
deleted file mode 100644
index f9dfbe16d64..00000000000
--- a/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Day_0_en.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:585d57c2b8e9a068fcd5e23ceb9e54ea4b87e7ed41035e31e13fe6869a0a6e06
-size 16842
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en.png
deleted file mode 100644
index 90cb82da361..00000000000
--- a/tests/uitests/src/test/snapshots/images/features.messages.impl.mentions_MentionSuggestionsPickerView_Night_0_en.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e581b1b19e882e164faba8dbfee8b37f91391ebf4bcda69fccb66439586ee24c
-size 16910
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png
new file mode 100644
index 00000000000..1d3db4da101
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d27b66d236df0754cc3d138e1c319223feaf1e58e1d0a11841eaec8d33af064f
+size 22373
diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png
new file mode 100644
index 00000000000..ddbe055b364
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dbb94e7c3af4ca7c1bb635c314d853d2c8575ca691f515b22a29b0ce72444475
+size 22549