From 4acefd4a903bd2820a856dc7dfdee81eff6cd537 Mon Sep 17 00:00:00 2001
From: Dima Rosmait <dima.rosmait@holi.team>
Date: Wed, 26 Feb 2025 15:39:23 +0000
Subject: [PATCH] HOLI-10819 - Contact space task

---
 apps/web/pages/chat/rooms/[roomId].tsx        |   2 +-
 core/domain/shared/queries.ts                 |   2 +
 core/i18n/locales/de.json                     |   9 +
 core/i18n/locales/en.json                     |   9 +
 core/screens/chat/ChatRoomView.tsx            | 247 ------------------
 .../chat/ChatRoomView/PrivateRoomView.tsx     |  98 +++++++
 .../chat/ChatRoomView/SpaceChildRoomView.tsx  |  84 ++++++
 .../chat/ChatRoomView/SpaceTaskRoomView.tsx   |  98 +++++++
 .../components/ChatRoomScreen.tsx             |  78 ++++++
 core/screens/chat/ChatRoomView/index.tsx      |  88 +++++++
 core/screens/chat/ChatRoomView/types.ts       |  25 ++
 core/screens/spaces/__tests__/testData.ts     |   1 +
 .../spaces/details/components/AskQuestion.tsx | 192 --------------
 .../AskQuestion/AskQuestionActionButton.tsx   |  51 ++++
 .../details/components/AskQuestion/index.tsx  |  74 ++++++
 .../details/components/StartSpaceChat.tsx     |  14 +-
 core/screens/spaces/details/queries.ts        |  10 +
 .../screens/spaces/hooks/useRepresentative.ts |   6 +-
 .../spaces/hooks/useSpaceAdminUsers.ts        |  17 ++
 core/screens/spaces/tasks/TaskDetails.tsx     |  28 +-
 .../ContactOrganizationActionButton.tsx       |  56 ++++
 .../ContactOrganization/StartChatButton.tsx   | 105 ++++++++
 .../components/ContactOrganization/index.tsx  | 167 ++++++++++++
 core/screens/spaces/tasks/tasks.graphql       |   1 +
 .../useTaskDetailsData/useTaskDetailsData.ts  |  11 +-
 core/screens/spaces/types.ts                  |   2 +-
 .../components/OwnConnectionsTab.tsx          |   4 +-
 .../components/StartChatButton.tsx            |  26 +-
 core/tracking/events.ts                       |  22 +-
 e2e/web/playwright.config.ts                  |   9 +
 e2e/web/tests/forgotPassword.spec.ts          |  10 +-
 e2e/web/tests/helpers.ts                      |   2 +-
 e2e/web/tests/helpers/api.ts                  |  88 +++++++
 e2e/web/tests/helpers/testimage.png           | Bin 0 -> 619 bytes
 e2e/web/tests/taskCollaboration.spec.ts       |  67 +++++
 e2e/web/tests/testmaster.ts                   |  29 +-
 holi-bricks/components/avatar/AvatarPile.tsx  |  59 +++++
 packages/api/graphql/graphql-codegen.ts       |   2 +
 packages/chat/src/client/ChatClient.ts        |  86 ++++--
 .../__tests__/ChatClient.createRoom.test.ts   |  42 ++-
 packages/chat/src/client/types.ts             |  34 ++-
 packages/chat/src/client/utils.ts             |  26 +-
 .../chat/src/components/ChatProfileCard.tsx   |  34 ++-
 .../src/components/ChatRoomActionDrawer.tsx   |  58 ++--
 .../src/components/ChatRoomHeaderTitle.tsx    |  10 +-
 .../chat/src/components/ChatTextInput.tsx     |  11 +-
 .../chat/src/components/RoomList/RoomItem.tsx |  16 +-
 .../RoomMessageList/RoomMessageListStart.tsx  | 102 +++++---
 packages/chat/src/store/store.ts              |  51 ++--
 49 files changed, 1607 insertions(+), 656 deletions(-)
 delete mode 100644 core/screens/chat/ChatRoomView.tsx
 create mode 100644 core/screens/chat/ChatRoomView/PrivateRoomView.tsx
 create mode 100644 core/screens/chat/ChatRoomView/SpaceChildRoomView.tsx
 create mode 100644 core/screens/chat/ChatRoomView/SpaceTaskRoomView.tsx
 create mode 100644 core/screens/chat/ChatRoomView/components/ChatRoomScreen.tsx
 create mode 100644 core/screens/chat/ChatRoomView/index.tsx
 create mode 100644 core/screens/chat/ChatRoomView/types.ts
 delete mode 100644 core/screens/spaces/details/components/AskQuestion.tsx
 create mode 100644 core/screens/spaces/details/components/AskQuestion/AskQuestionActionButton.tsx
 create mode 100644 core/screens/spaces/details/components/AskQuestion/index.tsx
 create mode 100644 core/screens/spaces/hooks/useSpaceAdminUsers.ts
 create mode 100644 core/screens/spaces/tasks/components/ContactOrganization/ContactOrganizationActionButton.tsx
 create mode 100644 core/screens/spaces/tasks/components/ContactOrganization/StartChatButton.tsx
 create mode 100644 core/screens/spaces/tasks/components/ContactOrganization/index.tsx
 create mode 100644 e2e/web/tests/helpers/testimage.png
 create mode 100644 e2e/web/tests/taskCollaboration.spec.ts
 create mode 100644 holi-bricks/components/avatar/AvatarPile.tsx

diff --git a/apps/web/pages/chat/rooms/[roomId].tsx b/apps/web/pages/chat/rooms/[roomId].tsx
index 28f029ac3c..8775c6786b 100644
--- a/apps/web/pages/chat/rooms/[roomId].tsx
+++ b/apps/web/pages/chat/rooms/[roomId].tsx
@@ -1,4 +1,4 @@
-import { NextPage } from 'next'
+import type { NextPage } from 'next'
 import dynamic from 'next/dynamic'
 import React from 'react'
 
diff --git a/core/domain/shared/queries.ts b/core/domain/shared/queries.ts
index a5e5e01c36..f411e6736a 100644
--- a/core/domain/shared/queries.ts
+++ b/core/domain/shared/queries.ts
@@ -44,6 +44,7 @@ export const topicsQuery = gql`
 export const UserAvatarFragment = gql`
   fragment UserAvatar on User {
     id
+    identity
     avatar
     avatarBlurhash
     avatarLabel
@@ -56,6 +57,7 @@ export const UserAvatarFragment = gql`
 export const FullUserAvatarFragment = gql`
   fragment FullUserAvatar on User {
     id
+    identity
     avatar
     avatarBlurhash
     avatarLabel
diff --git a/core/i18n/locales/de.json b/core/i18n/locales/de.json
index c11db9b807..5f64b7684f 100644
--- a/core/i18n/locales/de.json
+++ b/core/i18n/locales/de.json
@@ -152,6 +152,7 @@
   "chat.emptyRoom.was": "war",
   "chat.invitation.accept": "Chat starten",
   "chat.invitation.copy": "{{fullName}} möchte eine Unterhaltung mit dir beginnen",
+  "chat.invitation.copy.taskChat": "<0>{{fullName}}</0> möchte eine Unterhaltung mit dir zur Aufgabe <0>{{taskName}}</0> beginnen.",
   "chat.invitation.newconversation": "Neue Unterhaltung",
   "chat.invitation.reject": "Ablehnen",
   "chat.inviting": "Eingeladen",
@@ -166,11 +167,13 @@
   "chat.room.error.noRoom": "Der Chatraum konnte nicht gefunden werden.",
   "chat.room.error.openRoom": "Der Chatraum konnte nicht betreten werden.",
   "chat.room.input.placeholder": "Schreibe {{fullName}}",
+  "chat.room.input.placeholder.taskchat": "Schreibe eine Nachricht",
   "chat.room.lastMessage.membership.join": "{{user}} ist beigetreten",
   "chat.room.lastMessage.membership.leave": "{{user}} hat den Raum verlassen",
   "chat.room.noMessages": "Noch keine Nachrichten",
   "chat.room.sendButton.label": "Absenden",
   "chat.room.start.description": "Dies ist der Beginn deines Direkt-Chats mit <0>{{fullName}}</0>. Nur ihr beide seht diese Unterhaltung.",
+  "chat.room.start.group.description": "Dies ist der Beginn deines Chatverlaufs mit <fullName></fullName> von <spaceName>{{spaceName}}</spaceName>. Nur ihr seid Teil dieses Gespräches, es sei denn, jemand von euch lädt weitere Personen in den Chat ein.\n\nDieser Chat wurde erstellt, um die <taskName>{{taskName}}</taskName> Aufgabe zu besprechen.",
   "chat.start.button.label": "Schreiben",
   "chat.start.description": "Tippe den Namen der Person ein, mit der du einen Chat starten möchtest.",
   "chat.start.input.error": "Der Name der Person muss mindestens {{count}} Buchstaben haben.",
@@ -1416,6 +1419,11 @@
   "spaces.tabs.mine.membership_requestor": "Ausstehende Anfragen",
   "spaces.tasks.add.title": "Neue Aufgabe erstellen",
   "spaces.tasks.contact.title": "Ansprechpartner für diese Aufgabe",
+  "spaces.tasks.contactOrganization": "Nimm Kontakt auf",
+  "spaces.tasks.contactOrganization.title": "Starte mit einem \"Hallo\"",
+  "spaces.tasks.contactOrganization.description": "Du startest jetzt einen Chat mit <0>{{organization}}</0>'s Moderator*innen. Besprich mit ihnen die Details der Aufgabe und wie du mithelfen kannst!",
+  "spaces.tasks.contactOrganization.subdescription": "Kein Stress – Chatten ist nur der erste Schritt.",
+  "spaces.tasks.contactOrganization.cta": "Starte den Chat",
   "spaces.tasks.cta_claim": "Für Aufgabe anbieten…",
   "spaces.tasks.delete.confirmation.description": "Bist du sicher, dass du deine Aufgabe löschen willst?\nDiese Aktion ist unwiderruflich.",
   "spaces.tasks.delete.confirmation.title": "Aufgabe löschen",
@@ -1424,6 +1432,7 @@
   "spaces.tasks.details.space": "Für <0>{{space}}</0>",
   "spaces.tasks.details.title": "Aufgabendetails",
   "spaces.tasks.edit.title": "Aufgabe bearbeiten",
+  "spaces.tasks.goToExistingTaskChat": "Chat öffnen",
   "spaces.tasks.notFound": "Aufgabe {{taskId}} nicht gefunden",
   "spaces.tasks.published.by.title": "Aufgabe erstellt von",
   "spaces.tasks.spots_claimed": "{{taken}} von {{available}} Plätzen vergeben",
diff --git a/core/i18n/locales/en.json b/core/i18n/locales/en.json
index 8e2dfde968..23941b7fcc 100644
--- a/core/i18n/locales/en.json
+++ b/core/i18n/locales/en.json
@@ -152,6 +152,7 @@
   "chat.emptyRoom.was": "was",
   "chat.invitation.accept": "Start chatting",
   "chat.invitation.copy": "{{fullName}} wants to start a new conversation with you.",
+  "chat.invitation.copy.taskChat": "<0>{{fullName}}</0> wants to start a new conversation with you about the task <0>{{taskName}}</0>.",
   "chat.invitation.newconversation": "New conversation",
   "chat.invitation.reject": "Reject",
   "chat.inviting": "Inviting",
@@ -166,11 +167,13 @@
   "chat.room.error.noRoom": "The chat room could not be found.",
   "chat.room.error.openRoom": "The chat room could not be opened.",
   "chat.room.input.placeholder": "Message {{fullName}}",
+  "chat.room.input.placeholder.taskchat": "Write a message Message",
   "chat.room.lastMessage.membership.join": "{{user}} joined the room",
   "chat.room.lastMessage.membership.leave": "{{user}} left the room",
   "chat.room.noMessages": "No messages yet",
   "chat.room.sendButton.label": "Send message",
   "chat.room.start.description": "This is the beginning of your direct message history with <0>{{fullName}}</0>. Only the two of you see this conversation.",
+  "chat.room.start.group.description": "This is the beginning of your direct message history with <fullName></fullName> from <spaceName>{{spaceName}}</spaceName>. Only you are in this conversation, unless either of you invite anyone else to join.\n\nThis chat was created to discuss <taskName>{{taskName}}</taskName> task.",
   "chat.start.button.label": "Message",
   "chat.start.description": "Go ahead and type in the name of the person you want to start a chat with.",
   "chat.start.input.error": "The person's name needs to have at least {{count}} letters.",
@@ -1420,6 +1423,11 @@
   "spaces.tabs.mine.membership_requestor": "Pending requests",
   "spaces.tasks.add.title": "Create new task",
   "spaces.tasks.contact.title": "Contact person for this task",
+  "spaces.tasks.contactOrganization": "Contact organization",
+  "spaces.tasks.contactOrganization.title": "Start by simply saying \"Hi\"",
+  "spaces.tasks.contactOrganization.description": "You will now start a chat with <0>{{organization}}</0>'s admins. Feel free to discuss details of the task and how you can help out!",
+  "spaces.tasks.contactOrganization.subdescription": "No commitments – just a chance to connect.",
+  "spaces.tasks.contactOrganization.cta": "Start chatting",
   "spaces.tasks.cta_claim": "Ask to claim action…",
   "spaces.tasks.delete.confirmation.description": "Are you sure you want to delete this task?\nThis action is irreversible.",
   "spaces.tasks.delete.confirmation.title": "Delete your task",
@@ -1428,6 +1436,7 @@
   "spaces.tasks.details.space": "For <0>{{space}}</0>",
   "spaces.tasks.details.title": "Task details",
   "spaces.tasks.edit.title": "Edit task",
+  "spaces.tasks.goToExistingTaskChat": "Open chat",
   "spaces.tasks.notFound": "Task {{taskId}} not found",
   "spaces.tasks.published.by.title": "Task created by",
   "spaces.tasks.spots_claimed": "{{taken}} of {{available}} spots claimed",
diff --git a/core/screens/chat/ChatRoomView.tsx b/core/screens/chat/ChatRoomView.tsx
deleted file mode 100644
index 247be363ab..0000000000
--- a/core/screens/chat/ChatRoomView.tsx
+++ /dev/null
@@ -1,247 +0,0 @@
-import { useAtomValue } from 'jotai'
-import React, { useCallback, useEffect, useMemo } from 'react'
-import { useTranslation } from 'react-i18next'
-// eslint-disable-next-line no-restricted-imports
-import { Keyboard, Platform } from 'react-native'
-
-import { useChatInitialized } from '@holi/chat/src/client/chatState'
-import ChatProfileCard from '@holi/chat/src/components/ChatProfileCard'
-import ChatRoomActionDrawer from '@holi/chat/src/components/ChatRoomActionDrawer'
-import ChatRoomHeaderTitle from '@holi/chat/src/components/ChatRoomHeaderTitle'
-import { ChatTextInput } from '@holi/chat/src/components/ChatTextInput'
-import RoomMessageList from '@holi/chat/src/components/RoomMessageList'
-import RoomMessageListStart from '@holi/chat/src/components/RoomMessageList/RoomMessageListStart'
-import {
-  chatPartnerAtomCreator,
-  getHoliUserForChatMember,
-  isRoomCreator,
-  setLatestMessagesAsRead,
-  useRequestRoomMessages,
-  useRoomById,
-} from '@holi/chat/src/store'
-import { isSpaceChildChatRoom } from '@holi/chat/src/store/utils'
-import { getChatInitials, getDefaultAvatarColor } from '@holi/chat/src/utils'
-import { useMatrixIdToHoliUserRecord } from '@holi/chat/src/utils/hooks'
-import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
-import PermissionsGuard, { PermissionsType } from '@holi/core/components/PermissionsGuard'
-import { logError } from '@holi/core/errors/helpers'
-import { getInitials } from '@holi/core/helpers'
-import { useShowHeaderOnScreenFocus } from '@holi/core/helpers/useShowHeaderOnScreenFocus'
-import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
-import createParamHooks from '@holi/core/navigation/hooks/useParam'
-import useRouting from '@holi/core/navigation/hooks/useRouting'
-import HoliLoader from '@holi/ui/components/molecules/HoliLoader'
-import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
-import { TrackingEvent } from '@holi/core/tracking'
-import useTracking from '@holi/core/tracking/hooks/useTracking'
-import { Screen } from '@holi/core/components/Screen'
-
-const WAIT_FOR_CHATROOM_DELAY = 5000
-
-export type ChatRoomPageParams = {
-  roomId: string
-}
-
-const { useParam } = createParamHooks<ChatRoomPageParams>()
-
-// number of text messages needed to fill a screen and cause scrollbars
-const NUMBER_OF_INITIAL_MSGS = 40
-
-const ChatRoomView = () => {
-  const { t } = useTranslation()
-  const { navigateBack, replaceRoute } = useRouting()
-  const chatInitialized = useChatInitialized()
-  const { track } = useTracking()
-
-  const [roomId = ''] = useParam('roomId')
-  const [room, messages] = useRoomById(roomId)
-  const { user: loggedInUser } = useLoggedInUser()
-  const chatPartner = useAtomValue(useMemo(() => chatPartnerAtomCreator(roomId, loggedInUser), [loggedInUser, roomId]))
-
-  const requestMessages = useRequestRoomMessages(NUMBER_OF_INITIAL_MSGS)
-
-  const roomMemberIds = room?.members.map((m) => m.id) ?? []
-  const { matrixIdToHoliUserRecord, loading: loadingUsers } = useMatrixIdToHoliUserRecord(roomMemberIds)
-
-  const amIRoomCreator = loggedInUser && room && isRoomCreator(loggedInUser, room)
-  const chatPartnerHoliUser = getHoliUserForChatMember(chatPartner, matrixIdToHoliUserRecord)
-
-  const isSpaceRoom = room && isSpaceChildChatRoom(room)
-
-  const { openToast } = useToast()
-
-  const handleBackPress = useCallback(async () => {
-    await setLatestMessagesAsRead(roomId)
-
-    /** WORKAROUND: probably bug in `useAnimatedStyle` from `react-native-reanimated` for Android
-     * For Android we need wait till keyboard is hidden before navigating back
-     * Otherwise the `useAnimatedStyle` in `HoliKeyboardSafeAreaView` will not get updated
-     * and the view will be stuck in the keyboard position
-     */
-    if (Platform.OS === 'android' && Keyboard.isVisible()) {
-      const unsubscribe = Keyboard.addListener('keyboardDidHide', () => {
-        navigateBack('/chat')
-        unsubscribe?.remove()
-      })
-      Keyboard.dismiss()
-    } else {
-      navigateBack('/chat')
-    }
-  }, [navigateBack, roomId])
-
-  const headerTitle = () => {
-    if (loadingUsers) return null
-    switch (room?.type) {
-      case 'private':
-        return chatPartnerHoliUser ? (
-          <ChatRoomHeaderTitle
-            href={'/profile/' + chatPartnerHoliUser.id}
-            name={room.name}
-            imageSrc={chatPartner?.avatar}
-            imageBlurhash={chatPartnerHoliUser.avatarBlurhash}
-            initials={getChatInitials(chatPartner?.name)}
-            defaultColor={getDefaultAvatarColor(chatPartner?.name)}
-          />
-        ) : (
-          <ChatRoomHeaderTitle
-            name={room.name}
-            imageSrc={chatPartner?.avatar}
-            initials={getChatInitials(chatPartner?.name)}
-            defaultColor={getDefaultAvatarColor(chatPartner?.name)}
-          />
-        )
-
-      case 'space.child':
-        return (
-          <ChatRoomHeaderTitle
-            href={'/spaces/' + room.content.holiSpaceId}
-            name={room.content.spaceName}
-            initials={getInitials(room.content.spaceName)}
-            imageSrc={room.avatar}
-            defaultColor={room.content.avatarDefaultColor}
-            shape="square"
-          />
-        )
-
-      default:
-        return null
-    }
-  }
-
-  useEffect(() => {
-    if (chatInitialized && !room) {
-      // redirect to Chat screen if no room is found after WAIT_FOR_CHATROOM_DELAY ms
-      const timer = setTimeout(() => {
-        if (chatInitialized && !room) {
-          openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
-          replaceRoute('/chat').catch((error) =>
-            logError(error, 'Could not redirect logged in user to /chat', {
-              location: 'ChatRoomView.useEffect',
-            })
-          )
-        }
-      }, WAIT_FOR_CHATROOM_DELAY)
-      return () => clearTimeout(timer)
-    }
-  }, [chatInitialized, openToast, replaceRoute, t, room])
-
-  // request initial chunk of room messages
-  useEffect(() => {
-    if (!room?.id || room?.hasInvitation) return
-    requestMessages(roomId, false)
-  }, [requestMessages, roomId, room?.id, room?.hasInvitation])
-
-  // reset header when screen is focused
-  useShowHeaderOnScreenFocus()
-
-  const sendTrackingEvent = () => {
-    if (!loggedInUser || !room) return
-
-    if (isSpaceRoom) {
-      track(
-        TrackingEvent.Chat.MessageSent({
-          roomId: room.id,
-          type: 'space_message',
-          content: 'text',
-          spaceId: room.content.holiSpaceId,
-          spaceName: room.content.spaceName,
-        })
-      )
-    } else {
-      track(
-        TrackingEvent.Chat.MessageSent({
-          roomId: room.id,
-          type: 'direct_message',
-          content: 'text',
-        })
-      )
-    }
-  }
-
-  // TODO: provide skeleton instead of HoliLoader
-  if (!chatInitialized || !room || loadingUsers) return <HoliLoader testID={'chat-room-loader'} />
-
-  return (
-    <PermissionsGuard permissionType={PermissionsType.AUTH} redirectRoute="/chat">
-      <Screen
-        preset="fixed"
-        keyboardVerticalOffset={Platform.OS == 'ios' ? 168 : undefined} //FIXME: This is needed cause of a bug inside screen component that must be addressed.
-        StickyBottomComponent={
-          <ChatTextInput
-            isVisible={!room.hasInvitation}
-            userName={chatPartnerHoliUser?.fullName}
-            roomId={roomId}
-            onMessageSent={sendTrackingEvent}
-          />
-        }
-        contentContainerStyle={{ flex: 1 }}
-        headerOptions={{
-          headerTitle,
-          headerLeft: ({ tintColor }) => <HeaderBackButton tintColor={tintColor} onPress={handleBackPress} />,
-          headerRight: () =>
-            !!chatPartner &&
-            !isSpaceRoom && (
-              <ChatRoomActionDrawer
-                roomId={roomId}
-                chatPartner={chatPartner}
-                chatPartnerFullName={chatPartnerHoliUser?.fullName ?? chatPartner.name}
-              />
-            ),
-          navigationCustomOptions: {
-            footerShown: false,
-          },
-        }}
-      >
-        {room.hasInvitation ? (
-          <ChatProfileCard
-            member={chatPartner}
-            roomId={roomId}
-            user={chatPartnerHoliUser}
-            userIsLoading={loadingUsers}
-          />
-        ) : (
-          <RoomMessageList
-            roomId={room.id}
-            messages={messages || []}
-            matrixIdToHoliUserRecord={matrixIdToHoliUserRecord}
-            starter={
-              amIRoomCreator &&
-              chatPartnerHoliUser && (
-                <RoomMessageListStart member={chatPartner} chatPartnerHoliUser={chatPartnerHoliUser} />
-              )
-            }
-            onScroll={({ nativeEvent: { contentOffset, contentSize, layoutMeasurement } }) => {
-              const needToLoad = contentSize.height - contentOffset.y < layoutMeasurement.height * 2
-
-              if (needToLoad) {
-                requestMessages(roomId, false)
-              }
-            }}
-          />
-        )}
-      </Screen>
-    </PermissionsGuard>
-  )
-}
-
-export default ChatRoomView
diff --git a/core/screens/chat/ChatRoomView/PrivateRoomView.tsx b/core/screens/chat/ChatRoomView/PrivateRoomView.tsx
new file mode 100644
index 0000000000..16432a2144
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/PrivateRoomView.tsx
@@ -0,0 +1,98 @@
+import React from 'react'
+
+import ChatProfileCard from '@holi/chat/src/components/ChatProfileCard'
+import ChatRoomActionDrawer from '@holi/chat/src/components/ChatRoomActionDrawer'
+import ChatRoomHeaderTitle from '@holi/chat/src/components/ChatRoomHeaderTitle'
+import { ChatTextInput } from '@holi/chat/src/components/ChatTextInput'
+import RoomMessageListStart from '@holi/chat/src/components/RoomMessageList/RoomMessageListStart'
+import { isRoomCreator } from '@holi/chat/src/store'
+import { getChatInitials, getDefaultAvatarColor } from '@holi/chat/src/utils'
+import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
+import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
+import useTracking from '@holi/core/tracking/hooks/useTracking'
+
+import type { PrivateRoomViewProps } from './types'
+import ChatRoomScreen from '@holi/core/screens/chat/ChatRoomView/components/ChatRoomScreen'
+import { TrackingEvent } from '@holi/core/tracking'
+
+const PrivateRoomView = ({
+  room,
+  messages = [],
+  chatPartners,
+  chatPartnerHoliUsers,
+  matrixIdToHoliUserRecord,
+}: PrivateRoomViewProps) => {
+  const { track } = useTracking()
+
+  const { user: loggedInUser } = useLoggedInUser()
+  const amIRoomCreator = loggedInUser && room && isRoomCreator(loggedInUser, room)
+  const roomId = room.id
+
+  const sendTrackingEvent = () => {
+    track(
+      TrackingEvent.Chat.MessageSent({
+        roomId: room.id,
+        type: 'direct_message',
+        content: 'text',
+      })
+    )
+  }
+
+  const headerTitle = () => {
+    const chatPartner = chatPartners?.[0]
+    const chatPartnerHoliUser = chatPartnerHoliUsers[0]
+
+    return (
+      <ChatRoomHeaderTitle
+        href={'/profile/' + chatPartnerHoliUser.id}
+        name={room.name}
+        imageSrc={chatPartner?.avatar}
+        imageBlurhash={chatPartnerHoliUser.avatarBlurhash}
+        initials={getChatInitials(chatPartner?.name)}
+        defaultColor={getDefaultAvatarColor(chatPartner?.name)}
+      />
+    )
+  }
+
+  return (
+    <ChatRoomScreen
+      room={room}
+      messages={messages}
+      matrixIdToHoliUserRecord={matrixIdToHoliUserRecord}
+      StickyBottomComponent={
+        <ChatTextInput
+          isVisible={!room.hasInvitation}
+          userName={chatPartnerHoliUsers[0]?.fullName}
+          roomId={roomId}
+          onMessageSent={sendTrackingEvent}
+        />
+      }
+      headerOptions={{
+        headerTitle,
+        headerLeft: ({ tintColor }) => <HeaderBackButton tintColor={tintColor} />,
+        headerRight: () =>
+          !!chatPartners && (
+            <ChatRoomActionDrawer
+              roomId={roomId}
+              chatPartnerFullName={chatPartnerHoliUsers[0]?.fullName ?? chatPartners[0].name}
+              usersToBlock={chatPartnerHoliUsers}
+            />
+          ),
+        navigationCustomOptions: {
+          footerShown: false,
+        },
+      }}
+      starter={
+        amIRoomCreator &&
+        chatPartnerHoliUsers[0] && (
+          <RoomMessageListStart members={chatPartners} chatPartnerHoliUsers={chatPartnerHoliUsers} />
+        )
+      }
+      chatProfileCard={
+        <ChatProfileCard member={room.creator ?? undefined} roomId={roomId} user={chatPartnerHoliUsers[0]} />
+      }
+    />
+  )
+}
+
+export default PrivateRoomView
diff --git a/core/screens/chat/ChatRoomView/SpaceChildRoomView.tsx b/core/screens/chat/ChatRoomView/SpaceChildRoomView.tsx
new file mode 100644
index 0000000000..debe83afbc
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/SpaceChildRoomView.tsx
@@ -0,0 +1,84 @@
+import React from 'react'
+
+import ChatRoomHeaderTitle from '@holi/chat/src/components/ChatRoomHeaderTitle'
+import { ChatTextInput } from '@holi/chat/src/components/ChatTextInput'
+import RoomMessageListStart from '@holi/chat/src/components/RoomMessageList/RoomMessageListStart'
+import { isRoomCreator } from '@holi/chat/src/store'
+import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
+import { getInitials } from '@holi/core/helpers'
+import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
+import useTracking from '@holi/core/tracking/hooks/useTracking'
+
+import type { SpaceChildChatRoomProps } from './types'
+import ChatRoomScreen from './components/ChatRoomScreen'
+import { TrackingEvent } from '@holi/core/tracking'
+
+const SpaceChildRoomView = ({
+  room,
+  messages = [],
+  chatPartners,
+  chatPartnerHoliUsers,
+  matrixIdToHoliUserRecord,
+}: SpaceChildChatRoomProps) => {
+  const { track } = useTracking()
+
+  const { user: loggedInUser } = useLoggedInUser()
+  const amIRoomCreator = loggedInUser && room && isRoomCreator(loggedInUser, room)
+  const roomId = room.id
+
+  const sendTrackingEvent = () => {
+    track(
+      TrackingEvent.Chat.MessageSent({
+        roomId: room.id,
+        type: 'space_message',
+        content: 'text',
+        spaceId: room.content.holiSpaceId,
+        spaceName: room.content.spaceName,
+      })
+    )
+  }
+
+  const headerTitle = () => {
+    return (
+      <ChatRoomHeaderTitle
+        href={'/spaces/' + room.content.holiSpaceId}
+        name={room.content.spaceName}
+        initials={getInitials(room.content.spaceName)}
+        imageSrc={room.avatar}
+        defaultColor={room.content.avatarDefaultColor}
+        shape="square"
+      />
+    )
+  }
+
+  return (
+    <ChatRoomScreen
+      room={room}
+      messages={messages}
+      matrixIdToHoliUserRecord={matrixIdToHoliUserRecord}
+      StickyBottomComponent={
+        <ChatTextInput
+          isVisible={!room.hasInvitation}
+          userName={chatPartnerHoliUsers[0]?.fullName}
+          roomId={roomId}
+          onMessageSent={sendTrackingEvent}
+        />
+      }
+      headerOptions={{
+        headerTitle,
+        headerLeft: ({ tintColor }) => <HeaderBackButton tintColor={tintColor} />,
+        navigationCustomOptions: {
+          footerShown: false,
+        },
+      }}
+      starter={
+        amIRoomCreator &&
+        chatPartnerHoliUsers[0] && (
+          <RoomMessageListStart members={chatPartners} chatPartnerHoliUsers={chatPartnerHoliUsers} />
+        )
+      }
+    />
+  )
+}
+
+export default SpaceChildRoomView
diff --git a/core/screens/chat/ChatRoomView/SpaceTaskRoomView.tsx b/core/screens/chat/ChatRoomView/SpaceTaskRoomView.tsx
new file mode 100644
index 0000000000..4c6b2accf0
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/SpaceTaskRoomView.tsx
@@ -0,0 +1,98 @@
+import React from 'react'
+
+import ChatRoomHeaderTitle from '@holi/chat/src/components/ChatRoomHeaderTitle'
+import { ChatTextInput } from '@holi/chat/src/components/ChatTextInput'
+import RoomMessageListStart from '@holi/chat/src/components/RoomMessageList/RoomMessageListStart'
+import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
+import useTracking from '@holi/core/tracking/hooks/useTracking'
+
+import type { SpaceTaskChatRoomProps } from './types'
+import ChatRoomScreen from './components/ChatRoomScreen'
+import { TrackingEvent } from '@holi/core/tracking'
+import ChatProfileCard from '@holi/chat/src/components/ChatProfileCard'
+import ChatRoomActionDrawer from '@holi/chat/src/components/ChatRoomActionDrawer'
+import { useSpaceAdminUsers } from '@holi/core/screens/spaces/hooks/useSpaceAdminUsers'
+
+const SpaceTaskRoomView = ({
+  room,
+  messages = [],
+  chatPartners,
+  chatPartnerHoliUsers,
+  matrixIdToHoliUserRecord,
+}: SpaceTaskChatRoomProps) => {
+  const { track } = useTracking()
+
+  const roomId = room.id
+  const { taskId, taskName, holiSpaceId: spaceId, spaceName } = room.content
+
+  const { adminUsers = [] } = useSpaceAdminUsers(spaceId)
+  const chatPartnersWithoutAdmins = chatPartnerHoliUsers.filter(
+    (user) => !adminUsers.some((admin) => admin.id === user.id)
+  )
+
+  const sendTrackingEvent = () => {
+    track(
+      TrackingEvent.Chat.MessageSent({
+        roomId: room.id,
+        type: 'space_task_message',
+        content: 'text',
+        spaceId,
+        spaceName,
+        taskId,
+        taskName,
+      })
+    )
+  }
+
+  return (
+    <ChatRoomScreen
+      room={room}
+      messages={messages}
+      matrixIdToHoliUserRecord={matrixIdToHoliUserRecord}
+      StickyBottomComponent={
+        <ChatTextInput
+          isVisible={!room.hasInvitation}
+          userName={chatPartnerHoliUsers[0]?.fullName}
+          roomId={roomId}
+          isTaskChat
+          onMessageSent={sendTrackingEvent}
+        />
+      }
+      headerOptions={{
+        headerTitle: () => <ChatRoomHeaderTitle href={'/spaces/' + spaceId + '/tasks/' + taskId} name={taskName} />,
+        headerLeft: ({ tintColor }) => <HeaderBackButton tintColor={tintColor} />,
+        headerRight: () =>
+          !!chatPartners && (
+            <ChatRoomActionDrawer
+              roomId={roomId}
+              chatPartnerFullName={taskName}
+              usersToBlock={chatPartnersWithoutAdmins}
+            />
+          ),
+        navigationCustomOptions: {
+          footerShown: false,
+        },
+      }}
+      starter={
+        <RoomMessageListStart
+          members={chatPartners}
+          chatPartnerHoliUsers={chatPartnerHoliUsers}
+          taskName={taskName}
+          taskId={taskId}
+          parentSpaceId={spaceId}
+          spaceName={spaceName}
+        />
+      }
+      chatProfileCard={
+        <ChatProfileCard
+          member={room.creator ?? undefined}
+          roomId={roomId}
+          user={chatPartnerHoliUsers[0]}
+          taskName={taskName}
+        />
+      }
+    />
+  )
+}
+
+export default SpaceTaskRoomView
diff --git a/core/screens/chat/ChatRoomView/components/ChatRoomScreen.tsx b/core/screens/chat/ChatRoomView/components/ChatRoomScreen.tsx
new file mode 100644
index 0000000000..bed3b9caad
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/components/ChatRoomScreen.tsx
@@ -0,0 +1,78 @@
+import type React from 'react'
+import { useEffect } from 'react'
+import { Platform } from 'react-native'
+
+import RoomMessageList from '@holi/chat/src/components/RoomMessageList'
+import { useRequestRoomMessages } from '@holi/chat/src/store'
+import PermissionsGuard, { PermissionsType } from '@holi/core/components/PermissionsGuard'
+import { useShowHeaderOnScreenFocus } from '@holi/core/helpers/useShowHeaderOnScreenFocus'
+import { Screen } from '@holi/core/components/Screen'
+import type { ScreenProps } from '@holi/core/components/Screen/types'
+import type { ChatRoom, ChatRoomMessage, MatrixId } from '@holi/chat/src/client/types'
+import type { RoomMessageListProps } from '@holi/chat/src/components/RoomMessageList/types'
+import type { User } from '@holi/core/domain/shared/types'
+
+export interface ChatRoomScreenProps
+  extends Pick<ScreenProps, 'StickyBottomComponent' | 'headerOptions'>,
+    Pick<RoomMessageListProps, 'onScroll' | 'starter'> {
+  room: ChatRoom
+  messages?: ChatRoomMessage[]
+  chatProfileCard?: React.ReactNode
+  matrixIdToHoliUserRecord: Record<MatrixId, User | undefined>
+}
+
+// number of text messages needed to fill a screen and cause scrollbars
+const NUMBER_OF_INITIAL_MSGS = 40
+
+const ChatRoomScreen = ({
+  room,
+  messages = [],
+  StickyBottomComponent,
+  headerOptions,
+  starter: messageListStarter,
+  chatProfileCard,
+  matrixIdToHoliUserRecord,
+}: ChatRoomScreenProps) => {
+  const requestMessages = useRequestRoomMessages(NUMBER_OF_INITIAL_MSGS)
+
+  // request initial chunk of room messages
+  useEffect(() => {
+    if (room.hasInvitation) return
+    requestMessages(room.id, false)
+  }, [requestMessages, room.id, room.hasInvitation])
+
+  // reset header when screen is focused
+  useShowHeaderOnScreenFocus()
+
+  return (
+    <PermissionsGuard permissionType={PermissionsType.AUTH} redirectRoute="/chat">
+      <Screen
+        preset="fixed"
+        keyboardVerticalOffset={Platform.OS == 'ios' ? 168 : undefined} //FIXME: This is needed cause of a bug inside screen component that must be addressed.
+        StickyBottomComponent={StickyBottomComponent}
+        contentContainerStyle={{ flex: 1 }}
+        headerOptions={headerOptions}
+      >
+        {room.hasInvitation ? (
+          chatProfileCard
+        ) : (
+          <RoomMessageList
+            roomId={room.id}
+            messages={messages}
+            matrixIdToHoliUserRecord={matrixIdToHoliUserRecord}
+            starter={messageListStarter}
+            onScroll={({ nativeEvent: { contentOffset, contentSize, layoutMeasurement } }) => {
+              const needToLoad = contentSize.height - contentOffset.y < layoutMeasurement.height * 2
+
+              if (needToLoad) {
+                requestMessages(room.id, false)
+              }
+            }}
+          />
+        )}
+      </Screen>
+    </PermissionsGuard>
+  )
+}
+
+export default ChatRoomScreen
diff --git a/core/screens/chat/ChatRoomView/index.tsx b/core/screens/chat/ChatRoomView/index.tsx
new file mode 100644
index 0000000000..f08bdd02eb
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/index.tsx
@@ -0,0 +1,88 @@
+import { useAtomValue } from 'jotai'
+import React, { useEffect, useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+
+import { useChatInitialized } from '@holi/chat/src/client/chatState'
+import { chatPartnerAtomCreator, getHoliUserForChatMember, useRoomById } from '@holi/chat/src/store'
+import { useMatrixIdToHoliUserRecord } from '@holi/chat/src/utils/hooks'
+import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
+import { logError } from '@holi/core/errors/helpers'
+import createParamHooks from '@holi/core/navigation/hooks/useParam'
+import useRouting from '@holi/core/navigation/hooks/useRouting'
+import HoliLoader from '@holi/ui/components/molecules/HoliLoader'
+import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
+import PrivateRoomView from '@holi/core/screens/chat/ChatRoomView/PrivateRoomView'
+import SpaceChildRoomView from '@holi/core/screens/chat/ChatRoomView/SpaceChildRoomView'
+import { HoliError } from '@holi/core/errors/classes/HoliError'
+import SpaceTaskRoomView from '@holi/core/screens/chat/ChatRoomView/SpaceTaskRoomView'
+import type { User } from '@holi/core/domain/shared/types'
+
+const WAIT_FOR_CHATROOM_DELAY = 5000
+
+export type ChatRoomPageParams = {
+  roomId: string
+}
+
+const { useParam } = createParamHooks<ChatRoomPageParams>()
+
+const ChatRoomView = () => {
+  const { t } = useTranslation()
+  const { replaceRoute } = useRouting()
+  const { openToast } = useToast()
+
+  const chatInitialized = useChatInitialized()
+
+  const [roomId = ''] = useParam('roomId')
+  const { user: loggedInUser } = useLoggedInUser()
+  const [room, messages] = useRoomById(roomId)
+
+  const chatPartners =
+    useAtomValue(useMemo(() => chatPartnerAtomCreator(roomId, loggedInUser), [loggedInUser, roomId])) ?? []
+  const roomMemberIds = room?.members.map((m) => m.id) ?? []
+  const { matrixIdToHoliUserRecord, loading: loadingUsers } = useMatrixIdToHoliUserRecord(roomMemberIds)
+
+  const chatPartnerHoliUsers = chatPartners
+    .map((chatPartner) => getHoliUserForChatMember(chatPartner, matrixIdToHoliUserRecord))
+    .filter((holiUser) => holiUser !== undefined) as User[]
+
+  useEffect(() => {
+    if (chatInitialized && !room) {
+      // redirect to Chat screen if no room is found after WAIT_FOR_CHATROOM_DELAY ms
+      const timer = setTimeout(() => {
+        if (chatInitialized && !room) {
+          openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
+          replaceRoute('/chat').catch((error) =>
+            logError(error, 'Could not redirect logged in user to /chat', {
+              location: 'ChatRoomView.useEffect',
+            })
+          )
+        }
+      }, WAIT_FOR_CHATROOM_DELAY)
+      return () => clearTimeout(timer)
+    }
+  }, [chatInitialized, openToast, replaceRoute, t, room])
+
+  if (!chatInitialized || !room || loadingUsers) return <HoliLoader testID={'chat-room-loader'} />
+
+  const chatRoomViewProps = {
+    messages,
+    chatPartners,
+    chatPartnerHoliUsers,
+    loadingUsers,
+    matrixIdToHoliUserRecord,
+  } as const
+
+  switch (room.type) {
+    case 'private':
+      return <PrivateRoomView room={room} {...chatRoomViewProps} />
+    case 'space.child':
+      return <SpaceChildRoomView room={room} {...chatRoomViewProps} />
+    case 'space.task':
+      return <SpaceTaskRoomView room={room} {...chatRoomViewProps} />
+    case 'space':
+    default:
+      throw new HoliError(`Unsupported chat room type: ${room.type}`)
+  }
+}
+
+export default ChatRoomView
diff --git a/core/screens/chat/ChatRoomView/types.ts b/core/screens/chat/ChatRoomView/types.ts
new file mode 100644
index 0000000000..5f767fc1fd
--- /dev/null
+++ b/core/screens/chat/ChatRoomView/types.ts
@@ -0,0 +1,25 @@
+import type {
+  ChatRoomMember,
+  PrivateChatRoom,
+  SpaceChildChatRoom,
+  SpaceTaskChatRoom,
+} from '@holi/chat/src/client/types'
+import type { User } from '@holi/core/domain/shared/types'
+import type { ChatRoomScreenProps } from '@holi/core/screens/chat/ChatRoomView/components/ChatRoomScreen'
+
+interface BaseRoomViewProps extends Pick<ChatRoomScreenProps, 'messages' | 'matrixIdToHoliUserRecord'> {
+  chatPartners: ChatRoomMember[]
+  chatPartnerHoliUsers: User[]
+}
+
+export interface PrivateRoomViewProps extends BaseRoomViewProps {
+  room: PrivateChatRoom
+}
+
+export interface SpaceTaskChatRoomProps extends BaseRoomViewProps {
+  room: SpaceTaskChatRoom
+}
+
+export interface SpaceChildChatRoomProps extends BaseRoomViewProps {
+  room: SpaceChildChatRoom
+}
diff --git a/core/screens/spaces/__tests__/testData.ts b/core/screens/spaces/__tests__/testData.ts
index 62c16f80a3..feceb4606a 100644
--- a/core/screens/spaces/__tests__/testData.ts
+++ b/core/screens/spaces/__tests__/testData.ts
@@ -207,6 +207,7 @@ export const task: Task & WithTypename = {
   thumbnailBlurhash: '',
   skills,
   geolocation,
+  space: {} as Space,
   __typename: 'Task',
 }
 
diff --git a/core/screens/spaces/details/components/AskQuestion.tsx b/core/screens/spaces/details/components/AskQuestion.tsx
deleted file mode 100644
index a709adb681..0000000000
--- a/core/screens/spaces/details/components/AskQuestion.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-import { getOrCreateRoom } from '@holi/chat/src/store'
-import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
-import { ButtonTracked } from '@holi/core/components/Trackable'
-import { HoliError } from '@holi/core/errors/classes/HoliError'
-import { logError } from '@holi/core/errors/helpers'
-import { chatServerName } from '@holi/core/helpers/config'
-import useRouting from '@holi/core/navigation/hooks/useRouting'
-import { useSignupModal } from '@holi/core/providers/SignupModalProvider'
-import { useRepresentative } from '@holi/core/screens/spaces/hooks/useRepresentative'
-import { TrackingEvent } from '@holi/core/tracking'
-import { QuestionAnswerLine } from '@holi/icons/src/generated'
-import HoliLocalImage from '@holi/ui/components/atoms/HoliLocalImage'
-import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
-import HoliActionDrawer from '@holi/ui/components/organisms/HoliActionDrawer'
-import { useActionDrawerContext } from '@holi/ui/components/organisms/HoliActionDrawer/Drawer'
-import dimensions from '@holi/ui/styles/globalVars/dimensions'
-import { Text } from 'holi-bricks/components/text'
-import { useStyles } from 'holi-bricks/hooks'
-import { Spacing } from 'holi-bricks/tokens'
-import { createStyleSheet } from 'holi-bricks/utils'
-import React, { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { Platform, View } from 'react-native'
-
-const StartChatDrawerActionButton = ({ spaceIdOrName }: AskQuestionProps) => {
-  const { t } = useTranslation()
-  const { isLoggedIn } = useLoggedInUser()
-  const { hideDrawer } = useActionDrawerContext()
-  const { openSignupModal } = useSignupModal()
-  const { openToast } = useToast()
-  const { navigate } = useRouting()
-  const { representativeIdentity } = useRepresentative(spaceIdOrName)
-  const [isLoading, setIsLoading] = useState(false)
-
-  const safelyOpenSignupModal = () => {
-    /* prevent having two modals open as startChat shows another one for guest, which breaks on ios */
-    hideDrawer()
-
-    openSignupModal({
-      description: t('chat.start.loginRedirect.text'),
-      trackTrigger: 'START_CHAT',
-    })
-  }
-
-  async function openChatRoom(chatPartnerIdentity: string) {
-    try {
-      const chatPartnerMatrixId = '@' + chatPartnerIdentity + ':' + chatServerName
-      const { room_id } = await getOrCreateRoom([chatPartnerMatrixId])
-
-      if (!room_id) {
-        openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
-      } else {
-        navigate('/chat/rooms/' + room_id)
-        hideDrawer()
-      }
-    } catch (e) {
-      logError(e, 'Failed to start ask-question-chat with chat partner ' + chatPartnerIdentity, {
-        location: 'StartChatDrawerActionButton.openChatRoom',
-      })
-      openToast(t('chat.room.error.openRoom'), HoliToastType.ERROR)
-    }
-  }
-
-  const start = async () => {
-    setIsLoading(true)
-
-    if (!isLoggedIn) {
-      safelyOpenSignupModal()
-      setIsLoading(false)
-      return
-    }
-
-    if (!representativeIdentity) {
-      logError(
-        new HoliError('Can not create a chat without a space representatives identity'),
-        'Failed to start chat',
-        {
-          location: 'StartChatDrawerActionButton.start',
-        }
-      )
-      setIsLoading(false)
-      return
-    }
-
-    try {
-      await openChatRoom(representativeIdentity)
-    } finally {
-      setIsLoading(false)
-    }
-  }
-
-  return (
-    <ButtonTracked
-      trackingEvent={TrackingEvent.SpaceAskQuestion.askedQuestion}
-      loading={isLoading}
-      disabled={isLoading}
-      onPress={start}
-      variant="primary"
-      size="lg"
-      label={t('spaces.connectCta')}
-    />
-  )
-}
-
-type AskQuestionProps = {
-  spaceIdOrName?: string
-}
-
-const AskQuestionButton = (props: {
-  testID: string | undefined
-  onPress: () => void
-}) => {
-  const { styles } = useStyles(askQuestionButtonStyles)
-  const { t } = useTranslation()
-  const label = t('spaces.askInfo')
-  return (
-    <View
-      style={[
-        styles.ctaWrapper,
-        Platform.OS === 'web' && {
-          position: 'fixed',
-        },
-      ]}
-      testID={props.testID}
-    >
-      <ButtonTracked
-        trackingEvent={TrackingEvent.SpaceAskQuestion.startedConversation}
-        onPress={props.onPress}
-        variant="primary"
-        icon={QuestionAnswerLine}
-        size="lg"
-        label={label}
-      />
-    </View>
-  )
-}
-
-const askQuestionButtonStyles = createStyleSheet(() => ({
-  ctaWrapper: {
-    padding: Spacing.xxs,
-    left: 0,
-    right: 0,
-    bottom: 0,
-    maxWidth: Number(dimensions.maxScreenWidth),
-    position: 'absolute',
-    marginHorizontal: 'auto',
-  },
-}))
-
-export const AskQuestion = ({ spaceIdOrName }: AskQuestionProps) => {
-  const { styles } = useStyles(stylesheet)
-  const { t } = useTranslation()
-
-  if (!spaceIdOrName) return null
-
-  return (
-    <HoliActionDrawer.Drawer
-      renderCustomButton={(onPress, testID) => <AskQuestionButton testID={testID} onPress={onPress} />}
-      label={t('global.options')}
-      testID="ask-space-question"
-    >
-      <View style={styles.ctaDrawerContentWrapper}>
-        <HoliLocalImage
-          imageSource={require('@holi/ui/assets/img/spaces/chat.svg')}
-          isSvg
-          label="chat"
-          width={40}
-          height={36}
-          resizeMode="contain"
-        />
-
-        <View style={styles.ctaDrawerContent}>
-          <Text size="3xl">{t('spaces.connectTitle')}</Text>
-          <Text size="md">{t('spaces.connectDescription')}</Text>
-        </View>
-
-        <StartChatDrawerActionButton spaceIdOrName={spaceIdOrName} />
-      </View>
-    </HoliActionDrawer.Drawer>
-  )
-}
-
-const stylesheet = createStyleSheet(() => ({
-  ctaDrawerContent: {
-    flexDirection: 'column',
-    gap: 8,
-  },
-  ctaDrawerContentWrapper: {
-    flexDirection: 'column',
-    gap: 16,
-  },
-}))
diff --git a/core/screens/spaces/details/components/AskQuestion/AskQuestionActionButton.tsx b/core/screens/spaces/details/components/AskQuestion/AskQuestionActionButton.tsx
new file mode 100644
index 0000000000..0e21bc7b71
--- /dev/null
+++ b/core/screens/spaces/details/components/AskQuestion/AskQuestionActionButton.tsx
@@ -0,0 +1,51 @@
+import { ButtonTracked } from '@holi/core/components/Trackable'
+import { TrackingEvent } from '@holi/core/tracking'
+import { QuestionAnswerLine } from '@holi/icons/src/generated'
+import dimensions from '@holi/ui/styles/globalVars/dimensions'
+import { useStyles } from 'holi-bricks/hooks'
+import { Spacing } from 'holi-bricks/tokens'
+import { createStyleSheet } from 'holi-bricks/utils'
+import React, {} from 'react'
+import { Platform, View } from 'react-native'
+import { useTranslation } from 'react-i18next'
+
+export const AskQuestionActionButton = (props: {
+  testID: string | undefined
+  onPress: () => void
+}) => {
+  const { styles } = useStyles(askQuestionButtonStyles)
+  const { t } = useTranslation()
+  const label = t('spaces.askInfo')
+  return (
+    <View
+      style={[
+        styles.ctaWrapper,
+        Platform.OS === 'web' && {
+          position: 'fixed',
+        },
+      ]}
+      testID={props.testID}
+    >
+      <ButtonTracked
+        trackingEvent={TrackingEvent.SpaceAskQuestion.startedConversation}
+        onPress={props.onPress}
+        variant="primary"
+        icon={QuestionAnswerLine}
+        size="lg"
+        label={label}
+      />
+    </View>
+  )
+}
+
+const askQuestionButtonStyles = createStyleSheet(() => ({
+  ctaWrapper: {
+    padding: Spacing.xxs,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    maxWidth: Number(dimensions.maxScreenWidth),
+    position: 'absolute',
+    marginHorizontal: 'auto',
+  },
+}))
diff --git a/core/screens/spaces/details/components/AskQuestion/index.tsx b/core/screens/spaces/details/components/AskQuestion/index.tsx
new file mode 100644
index 0000000000..9c77e29438
--- /dev/null
+++ b/core/screens/spaces/details/components/AskQuestion/index.tsx
@@ -0,0 +1,74 @@
+import { toMatrixIdentity, getOrCreatePrivateRoom } from '@holi/chat/src/store'
+import { AskQuestionActionButton } from '@holi/core/screens/spaces/details/components/AskQuestion/AskQuestionActionButton'
+import { useRepresentative } from '@holi/core/screens/spaces/hooks/useRepresentative'
+import { StartChatButton } from '@holi/core/screens/spaces/tasks/components/ContactOrganization/StartChatButton'
+import { TrackingEvent } from '@holi/core/tracking'
+import HoliLocalImage from '@holi/ui/components/atoms/HoliLocalImage'
+import HoliActionDrawer from '@holi/ui/components/organisms/HoliActionDrawer'
+import { Text } from 'holi-bricks/components/text'
+import { useStyles } from 'holi-bricks/hooks'
+import { createStyleSheet } from 'holi-bricks/utils'
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { View } from 'react-native'
+
+export type AskQuestionProps = {
+  spaceIdOrName?: string
+}
+
+export const AskQuestion = ({ spaceIdOrName }: AskQuestionProps) => {
+  const { styles } = useStyles(stylesheet)
+  const { t } = useTranslation()
+  const { representative } = useRepresentative(spaceIdOrName)
+
+  const getOrCreateRoom = async () => {
+    if (!representative) return
+    const chatPartnerMatrixId = toMatrixIdentity(representative.identity)
+    const { room_id } = await getOrCreatePrivateRoom([chatPartnerMatrixId])
+    return room_id
+  }
+
+  if (!spaceIdOrName) return null
+
+  return (
+    <HoliActionDrawer.Drawer
+      renderCustomButton={(onPress, testID) => <AskQuestionActionButton testID={testID} onPress={onPress} />}
+      label={t('global.options')}
+      testID="ask-space-question"
+    >
+      <View style={styles.ctaDrawerContentWrapper}>
+        <HoliLocalImage
+          imageSource={require('@holi/ui/assets/img/spaces/chat.svg')}
+          isSvg
+          label="chat"
+          width={40}
+          height={36}
+          resizeMode="contain"
+        />
+
+        <View style={styles.ctaDrawerContent}>
+          <Text size="3xl">{t('spaces.connectTitle')}</Text>
+          <Text size="md">{t('spaces.connectDescription')}</Text>
+        </View>
+
+        <StartChatButton
+          chatPartners={representative ? [representative] : []}
+          label={t('spaces.connectCta')}
+          trackingEvent={TrackingEvent.SpaceAskQuestion.askedQuestion}
+          getRoomId={getOrCreateRoom}
+        />
+      </View>
+    </HoliActionDrawer.Drawer>
+  )
+}
+
+const stylesheet = createStyleSheet(() => ({
+  ctaDrawerContent: {
+    flexDirection: 'column',
+    gap: 8,
+  },
+  ctaDrawerContentWrapper: {
+    flexDirection: 'column',
+    gap: 16,
+  },
+}))
diff --git a/core/screens/spaces/details/components/StartSpaceChat.tsx b/core/screens/spaces/details/components/StartSpaceChat.tsx
index 95cf0f8adf..26dbaf3cee 100644
--- a/core/screens/spaces/details/components/StartSpaceChat.tsx
+++ b/core/screens/spaces/details/components/StartSpaceChat.tsx
@@ -1,4 +1,4 @@
-import { getOrCreateRoom } from '@holi/chat/src/store'
+import { getOrCreatePrivateRoom } from '@holi/chat/src/store'
 import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
 import { ButtonTracked } from '@holi/core/components/Trackable'
 import { HoliError } from '@holi/core/errors/classes/HoliError'
@@ -37,7 +37,7 @@ const ButtonWrapper = ({ spaceIdOrName, drawerButtonLabel }: ButtonWrapperProps)
   const { openSignupModal } = useSignupModal()
   const { openToast } = useToast()
   const { navigate } = useRouting()
-  const { representativeIdentity } = useRepresentative(spaceIdOrName)
+  const { representative } = useRepresentative(spaceIdOrName)
   const { t } = useTranslation()
 
   const [isLoading, setIsLoading] = useState(false)
@@ -45,7 +45,7 @@ const ButtonWrapper = ({ spaceIdOrName, drawerButtonLabel }: ButtonWrapperProps)
   async function openChatRoom(chatPartnerIdentity: string) {
     try {
       const chatPartnerMatrixId = '@' + chatPartnerIdentity + ':' + chatServerName
-      const { room_id } = await getOrCreateRoom([chatPartnerMatrixId])
+      const { room_id } = await getOrCreatePrivateRoom([chatPartnerMatrixId])
 
       if (!room_id) {
         openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
@@ -79,7 +79,7 @@ const ButtonWrapper = ({ spaceIdOrName, drawerButtonLabel }: ButtonWrapperProps)
       return
     }
 
-    if (!representativeIdentity) {
+    if (!representative?.identity) {
       logError(
         new HoliError('Can not create a chat without a space representatives identity'),
         'Failed to start chat',
@@ -92,7 +92,7 @@ const ButtonWrapper = ({ spaceIdOrName, drawerButtonLabel }: ButtonWrapperProps)
     }
 
     try {
-      await openChatRoom(representativeIdentity)
+      await openChatRoom(representative.identity)
     } finally {
       setIsLoading(false)
     }
@@ -100,7 +100,7 @@ const ButtonWrapper = ({ spaceIdOrName, drawerButtonLabel }: ButtonWrapperProps)
 
   return (
     <ButtonTracked
-      trackingEvent={TrackingEvent.TaskContactOrganization.startedConversation}
+      trackingEvent={TrackingEvent.TaskContactOrganization.startedConversationFromTask}
       loading={isLoading}
       disabled={isLoading}
       onPress={start}
@@ -124,7 +124,7 @@ export const StartSpaceChatButton = ({
     <HoliActionDrawer.Drawer
       renderCustomButton={(onPress, testID) => (
         <ButtonTracked
-          trackingEvent={TrackingEvent.TaskContactOrganization.contactOrganization}
+          trackingEvent={TrackingEvent.TaskContactOrganization.contactedOrganization}
           onPress={onPress}
           variant="primary"
           size="lg"
diff --git a/core/screens/spaces/details/queries.ts b/core/screens/spaces/details/queries.ts
index 088401c920..9aa5fd9cec 100644
--- a/core/screens/spaces/details/queries.ts
+++ b/core/screens/spaces/details/queries.ts
@@ -7,6 +7,7 @@ import {
   SpaceMemberListFragment,
   SpaceMembershipRequestListFragment,
 } from '@holi/core/screens/spaces/queries'
+import type { Task } from '@holi/core/screens/spaces/types'
 
 export const spaceMembersQuery = gql`
   ${SpaceMemberListFragment}
@@ -83,6 +84,15 @@ export const taskByIdQuery = gql`
   query taskById($id: UUID!) {
     taskById(id: $id) {
       ...FullTask
+      space {
+        id
+        name
+        title
+      }
     }
   }
 `
+
+export interface TaskByIdResponse {
+  taskById: Task
+}
diff --git a/core/screens/spaces/hooks/useRepresentative.ts b/core/screens/spaces/hooks/useRepresentative.ts
index 190eecbaeb..da2a0c2dc9 100644
--- a/core/screens/spaces/hooks/useRepresentative.ts
+++ b/core/screens/spaces/hooks/useRepresentative.ts
@@ -21,14 +21,12 @@ export const useRepresentative = (spaceIdOrName?: string) => {
     },
   })
 
-  const representative = data?.spaceById
-  const representativeIdentity =
-    representative && representative?.representatives?.length > 0 ? representative?.representatives[0] : undefined
+  const representative = data?.spaceById.representatives[0].user
 
   return {
     loading,
     error,
-    representativeIdentity: representativeIdentity?.user?.identity,
+    representative,
     called,
     refetch,
   }
diff --git a/core/screens/spaces/hooks/useSpaceAdminUsers.ts b/core/screens/spaces/hooks/useSpaceAdminUsers.ts
new file mode 100644
index 0000000000..63d116ca4b
--- /dev/null
+++ b/core/screens/spaces/hooks/useSpaceAdminUsers.ts
@@ -0,0 +1,17 @@
+import { useSpace } from '@holi/core/screens/spaces/hooks/useSpace'
+
+export const useSpaceAdminUsers = (spaceIdOrName?: string) => {
+  const { loading, error, space, refetch, called } = useSpace(spaceIdOrName)
+
+  const members = space?.members.data
+  const adminMembers = members?.filter((member) => member.isAdministrator)
+  const adminUsers = adminMembers?.map((admin) => admin.user)
+
+  return {
+    loading,
+    error,
+    adminUsers,
+    called,
+    refetch,
+  }
+}
diff --git a/core/screens/spaces/tasks/TaskDetails.tsx b/core/screens/spaces/tasks/TaskDetails.tsx
index fd88a2104c..2ffc640f6d 100644
--- a/core/screens/spaces/tasks/TaskDetails.tsx
+++ b/core/screens/spaces/tasks/TaskDetails.tsx
@@ -1,5 +1,4 @@
 import React, { memo, useCallback } from 'react'
-import { useTranslation } from 'react-i18next'
 import { Screen } from '@holi/core/components/Screen'
 import { DetailsScreen } from '@holi/core/layouts/DetailsScreen'
 import { Stack } from 'holi-bricks/components/stack'
@@ -8,7 +7,9 @@ import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
 import { TaskDrawer } from '@holi/core/screens/spaces/tasks/components/TaskDrawer'
 import { Platform } from 'react-native'
 import { white } from 'holi-bricks/tokens'
-import { StartSpaceChatButton } from '@holi/core/screens/spaces/details/components/StartSpaceChat'
+import { ContactOrganization } from '@holi/core/screens/spaces/tasks/components/ContactOrganization'
+import { useFeatureFlag } from '@holi/core/featureFlags/hooks/useFeatureFlag'
+import { FeatureFlagKey } from '@holi/core/featureFlags/constants'
 
 export type TaskDetailsParams = {
   taskId: string
@@ -21,8 +22,10 @@ const TaskDetailsContainer = () => {
     spaceName,
     id,
     refetch,
+    spaceId,
+    spaceTitle,
   } = useTaskDetailsData()
-  const { t } = useTranslation()
+  const displayCta = useFeatureFlag(FeatureFlagKey.TASK_CONTACT_CTA)
 
   const renderActionDrawer = useCallback(
     (tintColor?: string) => <TaskDrawer tintColor={tintColor} isOwner={isOwner} spaceName={spaceName} taskId={id} />,
@@ -59,16 +62,15 @@ const TaskDetailsContainer = () => {
           alignSelf="center"
           width="100%"
         >
-          <StartSpaceChatButton
-            preDrawerButtonProps={{
-              label: t('task.cta'),
-              inline: Platform.OS === 'web',
-            }}
-            spaceIdOrName={spaceName}
-            drawerTitle={t('task.startChat.drawer.title')}
-            drawerContent={t('task.startChat.drawer.content', { spaceName: spaceName })}
-            drawerButtonLabel={t('task.startChat.drawer.cta')}
-          />
+          {displayCta && (
+            <ContactOrganization
+              spaceIdOrName={spaceName}
+              taskName={title}
+              taskId={id}
+              spaceTitle={spaceTitle}
+              spaceId={spaceId}
+            />
+          )}
         </Stack>
       }
     >
diff --git a/core/screens/spaces/tasks/components/ContactOrganization/ContactOrganizationActionButton.tsx b/core/screens/spaces/tasks/components/ContactOrganization/ContactOrganizationActionButton.tsx
new file mode 100644
index 0000000000..c50a7dad15
--- /dev/null
+++ b/core/screens/spaces/tasks/components/ContactOrganization/ContactOrganizationActionButton.tsx
@@ -0,0 +1,56 @@
+import { ButtonTracked } from '@holi/core/components/Trackable'
+import { TrackingEvent } from '@holi/core/tracking'
+import { ChatSmile2 } from '@holi/icons/src/generated'
+import dimensions from '@holi/ui/styles/globalVars/dimensions'
+import { useStyles } from 'holi-bricks/hooks'
+import { Spacing } from 'holi-bricks/tokens'
+import { createStyleSheet } from 'holi-bricks/utils'
+import React, {} from 'react'
+import { Platform, View } from 'react-native'
+import { useTranslation } from 'react-i18next'
+
+interface Props {
+  onPress: () => void
+  loading?: boolean
+  testID?: string
+}
+
+export const ContactOrganizationActionButton = ({ onPress, testID, loading }: Props) => {
+  const { styles } = useStyles(stylesheet)
+  const { t } = useTranslation()
+
+  return (
+    <View
+      style={[
+        styles.ctaWrapper,
+        Platform.OS === 'web' && {
+          position: 'fixed',
+        },
+      ]}
+      testID={testID}
+    >
+      <ButtonTracked
+        trackingEvent={TrackingEvent.TaskContactOrganization.startedConversationFromTask}
+        onPress={onPress}
+        loading={loading}
+        disabled={loading}
+        variant="primary"
+        icon={ChatSmile2}
+        size="lg"
+        label={t('spaces.tasks.contactOrganization')}
+      />
+    </View>
+  )
+}
+
+const stylesheet = createStyleSheet(() => ({
+  ctaWrapper: {
+    padding: Spacing.xxs,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    maxWidth: Number(dimensions.maxScreenWidth),
+    position: 'absolute',
+    marginHorizontal: 'auto',
+  },
+}))
diff --git a/core/screens/spaces/tasks/components/ContactOrganization/StartChatButton.tsx b/core/screens/spaces/tasks/components/ContactOrganization/StartChatButton.tsx
new file mode 100644
index 0000000000..8442f57376
--- /dev/null
+++ b/core/screens/spaces/tasks/components/ContactOrganization/StartChatButton.tsx
@@ -0,0 +1,105 @@
+import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
+import { ButtonTracked } from '@holi/core/components/Trackable'
+import { HoliError } from '@holi/core/errors/classes/HoliError'
+import { logError } from '@holi/core/errors/helpers'
+import useRouting from '@holi/core/navigation/hooks/useRouting'
+import { useSignupModal } from '@holi/core/providers/SignupModalProvider'
+import type { TrackingEvent } from '@holi/core/tracking'
+import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
+import { useActionDrawerContext } from '@holi/ui/components/organisms/HoliActionDrawer/Drawer'
+import React, { useState } from 'react'
+
+import { useTranslation } from 'react-i18next'
+import type { User } from '@holi/core/domain/shared/types'
+import type { HoliIconType } from '@holi/icons/src/HoliIconType'
+
+type StartChatButtonProps = {
+  chatPartners?: User[]
+  icon?: HoliIconType
+  label: string
+  trackingEvent: TrackingEvent
+  getRoomId: () => Promise<string | undefined>
+}
+
+export const StartChatButton = ({
+  chatPartners,
+  icon,
+  label,
+  trackingEvent,
+  getRoomId: getOrCreateRoom,
+}: StartChatButtonProps) => {
+  const { t } = useTranslation()
+  const { isLoggedIn } = useLoggedInUser()
+  const { hideDrawer } = useActionDrawerContext()
+  const { openSignupModal } = useSignupModal()
+  const { openToast } = useToast()
+  const { navigate } = useRouting()
+
+  const [isLoading, setIsLoading] = useState(false)
+
+  const safelyOpenSignupModal = () => {
+    /* prevent having two modals open as startChat shows another one for guest, which breaks on ios */
+    hideDrawer()
+
+    openSignupModal({
+      description: t('chat.start.loginRedirect.text'),
+      trackTrigger: 'START_CHAT',
+    })
+  }
+
+  async function openChatRoom(chatPartners: User[]) {
+    try {
+      const room_id = await getOrCreateRoom()
+
+      if (!room_id) {
+        openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
+      } else {
+        navigate('/chat/rooms/' + room_id)
+        hideDrawer()
+      }
+    } catch (e) {
+      logError(e, 'Failed to start contact-organization-chat with chat partner ' + chatPartners, {
+        location: 'StartChatDrawerActionButton.openChatRoom',
+      })
+      openToast(t('chat.room.error.openRoom'), HoliToastType.ERROR)
+    }
+  }
+
+  const startChat = async () => {
+    if (!isLoggedIn) {
+      safelyOpenSignupModal()
+      return
+    }
+
+    if (!chatPartners) {
+      logError(
+        new HoliError('Can not create a chat without a space representatives identity'),
+        'Failed to start chat',
+        {
+          location: 'StartChatDrawerActionButton.start',
+        }
+      )
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      await openChatRoom(chatPartners)
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  return (
+    <ButtonTracked
+      trackingEvent={trackingEvent}
+      loading={isLoading}
+      disabled={isLoading}
+      onPress={startChat}
+      variant="primary"
+      size="lg"
+      icon={icon}
+      label={label}
+    />
+  )
+}
diff --git a/core/screens/spaces/tasks/components/ContactOrganization/index.tsx b/core/screens/spaces/tasks/components/ContactOrganization/index.tsx
new file mode 100644
index 0000000000..6ea6a7eb36
--- /dev/null
+++ b/core/screens/spaces/tasks/components/ContactOrganization/index.tsx
@@ -0,0 +1,167 @@
+import { getExistingRoomId, getOrCreateSpaceTaskRoom, toMatrixIdentity } from '@holi/chat/src/store'
+import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
+import useRouting from '@holi/core/navigation/hooks/useRouting'
+import HoliLocalImage from '@holi/ui/components/atoms/HoliLocalImage'
+import HoliActionDrawer from '@holi/ui/components/organisms/HoliActionDrawer'
+import dimensions from '@holi/ui/styles/globalVars/dimensions'
+import { Text } from 'holi-bricks/components/text'
+import { Button } from 'holi-bricks/components/button'
+import { useStyles } from 'holi-bricks/hooks'
+import { Spacing } from 'holi-bricks/tokens'
+import { createStyleSheet } from 'holi-bricks/utils'
+import React from 'react'
+import { Platform, View } from 'react-native'
+import { Trans, useTranslation } from 'react-i18next'
+import { useSpaceAdminUsers } from '@holi/core/screens/spaces/hooks/useSpaceAdminUsers'
+import { useChatInitialized } from '@holi/chat/src/client/chatState'
+
+import { StartChatButton } from './StartChatButton'
+import { ContactOrganizationActionButton } from './ContactOrganizationActionButton'
+import { TrackingEvent } from '@holi/core/tracking'
+import { ChatSmile2 } from '@holi/icons/src/generated'
+import { useErrorHandling } from '@holi/core/errors/hooks'
+
+interface Props {
+  spaceIdOrName?: string
+  taskName: string
+  taskId?: string
+  spaceTitle?: string
+  spaceId?: string
+  alreadyStarted?: boolean
+}
+
+export const ContactOrganization = ({ spaceIdOrName, spaceId, spaceTitle, taskId, taskName }: Props) => {
+  const { styles } = useStyles(stylesheet)
+  const { t } = useTranslation()
+  const { adminUsers, loading: adminUsersLoading } = useSpaceAdminUsers(spaceIdOrName)
+  const { user: loggedInUser } = useLoggedInUser()
+  const isChatInitialized = useChatInitialized()
+  const { displayError } = useErrorHandling()
+
+  const { navigate } = useRouting()
+
+  if (!adminUsers) return null
+
+  const getOrCreateRoom = async () => {
+    if (!spaceId || !taskId || !spaceTitle) {
+      displayError(new Error('Cannot create a task room chat'), 'Cannot create a task room chat', {
+        location: 'ContactOrganization.getOrCreateRoom',
+        spaceIdOrName,
+      })
+      return undefined
+    }
+
+    const chatPartnerMatrixIds = adminUsers.map(({ identity }) => toMatrixIdentity(identity))
+    const { room_id } = await getOrCreateSpaceTaskRoom(chatPartnerMatrixIds, {
+      type: 'space.task',
+      content: {
+        taskId: taskId,
+        taskName: taskName,
+        spaceName: spaceTitle,
+        holiSpaceId: spaceId,
+      },
+    })
+    return room_id
+  }
+
+  const findExistingRoomId = () => {
+    if (!loggedInUser) return undefined
+    if (!isChatInitialized) return undefined
+    if (!adminUsers) return undefined
+
+    const adminMatrixUserIds = adminUsers.map(({ identity }) => toMatrixIdentity(identity))
+    return getExistingRoomId(adminMatrixUserIds, 'space.task')
+  }
+  const existingRoomId = findExistingRoomId()
+
+  const isSpaceAdmin = adminUsers?.some((admin) => admin.id === loggedInUser?.id)
+  if (!spaceIdOrName || isSpaceAdmin) return null
+
+  if (existingRoomId) {
+    return (
+      <View
+        style={[
+          styles.ctaWrapper,
+          Platform.OS === 'web' && {
+            position: 'fixed',
+          },
+        ]}
+      >
+        <Button
+          label={t('spaces.tasks.goToExistingTaskChat')}
+          onPress={() => navigate('/chat/rooms/' + existingRoomId)}
+        />
+      </View>
+    )
+  }
+
+  return (
+    <HoliActionDrawer.Drawer
+      renderCustomButton={(onPress, testID) => (
+        <ContactOrganizationActionButton
+          loading={!isChatInitialized || adminUsersLoading}
+          testID={testID}
+          onPress={onPress}
+        />
+      )}
+      label={t('global.options')}
+      testID="contact-organization-drawer"
+    >
+      <View style={styles.ctaDrawerContentWrapper}>
+        <View style={styles.ctaDrawerContent}>
+          <HoliLocalImage
+            imageSource={require('@holi/ui/assets/img/spaces/chat.svg')}
+            isSvg
+            label="chat"
+            width={40}
+            height={40}
+            resizeMode="contain"
+          />
+
+          <Text size="3xl">{t('spaces.tasks.contactOrganization.title')}</Text>
+
+          <Text size="md" color="support">
+            <Trans t={t} i18nKey="spaces.tasks.contactOrganization.description" values={{ organization: spaceTitle }}>
+              <Text size="lg" textAlign="center" />
+            </Trans>
+          </Text>
+
+          <Text size="sm" color="support">
+            {t('spaces.tasks.contactOrganization.subdescription')}
+          </Text>
+        </View>
+
+        <StartChatButton
+          chatPartners={adminUsers}
+          trackingEvent={TrackingEvent.TaskContactOrganization.contactedOrganization}
+          icon={ChatSmile2}
+          label={t('spaces.tasks.contactOrganization.cta')}
+          getRoomId={getOrCreateRoom}
+        />
+      </View>
+    </HoliActionDrawer.Drawer>
+  )
+}
+
+const stylesheet = createStyleSheet({
+  ctaDrawerContent: {
+    flexDirection: 'column',
+    gap: Spacing['3xs'],
+  },
+  ctaDrawerContentWrapper: {
+    flexDirection: 'column',
+    gap: Spacing.xs,
+  },
+  image: {
+    alignSelf: 'center',
+  },
+  ctaWrapper: {
+    padding: Spacing.xxs,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    maxWidth: Number(dimensions.maxScreenWidth),
+    position: 'absolute',
+    marginHorizontal: 'auto',
+  },
+})
diff --git a/core/screens/spaces/tasks/tasks.graphql b/core/screens/spaces/tasks/tasks.graphql
index 64a7164da1..906517e317 100644
--- a/core/screens/spaces/tasks/tasks.graphql
+++ b/core/screens/spaces/tasks/tasks.graphql
@@ -24,6 +24,7 @@ query taskById($id: UUID!) {
       id
       connectionStatusToMyself
       name
+      title
       avatar
       avatarBlurhash
       topics {
diff --git a/core/screens/spaces/tasks/useTaskDetailsData/useTaskDetailsData.ts b/core/screens/spaces/tasks/useTaskDetailsData/useTaskDetailsData.ts
index b85f51f157..494b2c7d21 100644
--- a/core/screens/spaces/tasks/useTaskDetailsData/useTaskDetailsData.ts
+++ b/core/screens/spaces/tasks/useTaskDetailsData/useTaskDetailsData.ts
@@ -9,6 +9,7 @@ import { CalendarEvent, MapPin2, UserFilled } from '@holi/icons/src/generated'
 import { useTranslation } from 'react-i18next'
 import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
 import { useRef } from 'react'
+import { getFormattedDate } from '@holi/core/i18n/helpers/dateHelper'
 
 export type TaskDetailsParams = {
   spaceIdOrName: string
@@ -21,6 +22,8 @@ export type UseTaskDetailsData = {
   spaceName?: string
   isOwner?: boolean
   refetch(): Promise<void>
+  spaceId?: string
+  spaceTitle?: string
 }
 
 const { useParam, useUUIDParam } = createParamHooks<TaskDetailsParams>()
@@ -30,7 +33,7 @@ export const useTaskDetailsData = (): UseTaskDetailsData => {
   const [spaceIdOrName] = useParam('spaceIdOrName')
   const { displayError } = useErrorHandling()
   const { replaceRoute } = useRouting()
-  const { t } = useTranslation()
+  const { t, i18n } = useTranslation()
   const [taskId] = useUUIDParam('taskId')
   const loginState = useLoggedInUser()
   const { data, loading, refetch } = useTaskByIdQuery({
@@ -100,6 +103,8 @@ export const useTaskDetailsData = (): UseTaskDetailsData => {
 
   const infoList = [locationType, slots, regularity].filter((detail) => detail !== undefined) as DetailsScreenInfoList[]
 
+  const formattedDate = getFormattedDate(data?.taskById?.created, i18n.language)
+
   return {
     spaceName: data?.taskById?.space?.name,
     refetch: async () => {
@@ -123,7 +128,7 @@ export const useTaskDetailsData = (): UseTaskDetailsData => {
         uri: data?.taskById?.thumbnail,
       },
       author: {
-        topTitle: `${t('postedOnHoli')} ∙ ${new Date(data?.taskById?.created).toLocaleDateString('en')}`,
+        topTitle: `${formattedDate} ∙ ${t('postedOnHoli')}`,
         title: data?.taskById?.space?.name || data?.taskById?.name || '??',
         subtitle: data?.taskById?.creator?.fullName,
         mainAvatar: {
@@ -138,5 +143,7 @@ export const useTaskDetailsData = (): UseTaskDetailsData => {
           : undefined,
       },
     },
+    spaceId: data?.taskById?.space?.id,
+    spaceTitle: data?.taskById?.space?.title,
   }
 }
diff --git a/core/screens/spaces/types.ts b/core/screens/spaces/types.ts
index 0f15b679a3..44f967cc39 100644
--- a/core/screens/spaces/types.ts
+++ b/core/screens/spaces/types.ts
@@ -162,7 +162,7 @@ export interface Task {
   geolocation?: GeolocationPoint
   thumbnail?: string
   thumbnailBlurhash?: string
-  space?: Space
+  space: Space
 }
 
 export enum VisibilityType {
diff --git a/core/screens/userprofile/components/OwnConnectionsTab.tsx b/core/screens/userprofile/components/OwnConnectionsTab.tsx
index 3e25dcc210..a14c2634a0 100644
--- a/core/screens/userprofile/components/OwnConnectionsTab.tsx
+++ b/core/screens/userprofile/components/OwnConnectionsTab.tsx
@@ -31,7 +31,7 @@ import { FeatureFlagKey } from '@holi/core/featureFlags/constants'
 import { useChatInitialized } from '@holi/chat/src/client/chatState'
 import { HoliError } from '@holi/core/errors/classes/HoliError'
 import { chatServerName } from '@holi/core/helpers/config'
-import { getOrCreateRoom } from '@holi/chat/src/store'
+import { getOrCreatePrivateRoom } from '@holi/chat/src/store'
 import { useUserConnection } from '@holi/core/helpers'
 import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
 import useTracking from '@holi/core/tracking/hooks/useTracking'
@@ -126,7 +126,7 @@ const ConnectionsTab = ({ emptyText, emptyCallToAction }: ConnectionsTabProps) =
     try {
       // TODO: the correct Matrix identity should come from Okuna
       const fullIdentity = '@' + identity + ':' + chatServerName
-      const { room_id } = await getOrCreateRoom([fullIdentity])
+      const { room_id } = await getOrCreatePrivateRoom([fullIdentity])
 
       if (!room_id) {
         openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
diff --git a/core/screens/userprofile/components/StartChatButton.tsx b/core/screens/userprofile/components/StartChatButton.tsx
index 538f673b6e..a9cabeb1b0 100644
--- a/core/screens/userprofile/components/StartChatButton.tsx
+++ b/core/screens/userprofile/components/StartChatButton.tsx
@@ -2,29 +2,25 @@ import React from 'react'
 import { useTranslation } from 'react-i18next'
 
 import { useChatInitialized } from '@holi/chat/src/client/chatState'
-import { getOrCreateRoom } from '@holi/chat/src/store'
+import { getOrCreatePrivateRoom, toMatrixIdentity } from '@holi/chat/src/store'
 import { useLoggedInUser } from '@holi/core/auth/hooks/useLoggedInUser'
 import type { User } from '@holi/core/domain/shared/types'
 import { HoliError } from '@holi/core/errors/classes/HoliError'
 import { logError } from '@holi/core/errors/helpers'
 import { FeatureFlagKey } from '@holi/core/featureFlags/constants'
 import { useFeatureFlagWithMaintenance } from '@holi/core/featureFlags/hooks/useFeatureFlagWithMaintenance'
-import { chatServerName } from '@holi/core/helpers/config'
 import useRouting from '@holi/core/navigation/hooks/useRouting'
 import { useSignupModal } from '@holi/core/providers/SignupModalProvider'
 import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
 import { Button, type ButtonProps } from 'holi-bricks/components/button'
 
-export function StartChatButton({
-  label,
-  doReplaceRoute,
-  user,
-  ...rest
-}: {
+interface Props extends ButtonProps {
   user: User | null
   label: string
   doReplaceRoute?: boolean
-} & ButtonProps) {
+}
+
+export function StartChatButton({ label, doReplaceRoute, user: chatPartnerUser, ...rest }: Props) {
   const { t } = useTranslation()
   const chatFeatureFlag = useFeatureFlagWithMaintenance(FeatureFlagKey.CHAT)
   const isChatInitialized = useChatInitialized()
@@ -35,8 +31,6 @@ export function StartChatButton({
   const { openToast } = useToast()
   const { replaceRoute, navigate } = useRouting()
 
-  const identity = user?.identity
-
   const handleChat = async () => {
     if (!isLoggedIn) {
       return openSignupModal({
@@ -45,8 +39,8 @@ export function StartChatButton({
       })
     }
 
-    if (!identity) {
-      logError(new HoliError('Can not create a chat without a user identity'), 'Failed to open chat', {
+    if (!chatPartnerUser) {
+      logError(new HoliError('Can not create a chat without chat partner user'), 'Failed to open chat', {
         location: 'StartChatButton.handleChat',
       })
       return
@@ -54,8 +48,8 @@ export function StartChatButton({
 
     try {
       // TODO: the correct Matrix identity should come from Okuna
-      const fullIdentity = '@' + identity + ':' + chatServerName
-      const { room_id } = await getOrCreateRoom([fullIdentity])
+      const chatMemberMatrixId = toMatrixIdentity(chatPartnerUser.identity)
+      const { room_id } = await getOrCreatePrivateRoom([chatMemberMatrixId])
 
       if (!room_id) {
         openToast(t('chat.room.error.noRoom'), HoliToastType.ERROR)
@@ -73,7 +67,7 @@ export function StartChatButton({
 
   return (
     <Button
-      disabled={!chatFeatureFlag.isOn || !isChatInitialized || !identity}
+      disabled={!chatFeatureFlag.isOn || !isChatInitialized || !chatPartnerUser}
       label={label}
       onPress={handleChat}
       testID="user-profile-open-chat"
diff --git a/core/tracking/events.ts b/core/tracking/events.ts
index 4ae24da2a3..592cf08c1d 100644
--- a/core/tracking/events.ts
+++ b/core/tracking/events.ts
@@ -104,22 +104,22 @@ export namespace TrackingEvent {
 
   export const SpaceAskQuestion = {
     askedQuestion: {
-      name: 'askQuestionButton_pressed',
+      name: 'space_askQuestionButton_pressed',
       ...versionOne,
     },
     startedConversation: {
-      name: 'startConversationButton_pressed',
+      name: 'space_startConversationButton_pressed',
       ...versionOne,
     },
   }
 
   export const TaskContactOrganization = {
-    contactOrganization: {
-      name: 'contactOrganization_pressed',
+    contactedOrganization: {
+      name: 'task_contactOrganisationButton_pressed',
       ...versionOne,
     },
-    startedConversation: {
-      name: 'startConversationButton_pressed',
+    startedConversationFromTask: {
+      name: 'task_startConversationButton_pressed',
       ...versionOne,
     },
   }
@@ -427,7 +427,15 @@ export namespace TrackingEvent {
       spaceName: string
     }
 
-    type ChatMessageMetadata = ChatUserMessageMetadata | ChatSpaceMessageMetadata
+    interface ChatSpaceTaskMessageMetadata extends ChatMessageMetadataBase {
+      type: 'space_task_message'
+      spaceId: string
+      spaceName: string
+      taskName: string
+      taskId: string
+    }
+
+    type ChatMessageMetadata = ChatUserMessageMetadata | ChatSpaceMessageMetadata | ChatSpaceTaskMessageMetadata
 
     export const MessageSent = (metadata: ChatMessageMetadata): TrackingEvent => ({
       name: 'chatMessageSent',
diff --git a/e2e/web/playwright.config.ts b/e2e/web/playwright.config.ts
index 6fb70bdf06..5cfd924e84 100644
--- a/e2e/web/playwright.config.ts
+++ b/e2e/web/playwright.config.ts
@@ -89,6 +89,15 @@ const config: PlaywrightTestConfig = {
     /* Base URL to use in actions like `await page.goto('/')`. */
     baseURL: baseURL,
     headless: process.env.CI ? true : !!process.env.HEADLESS || true,
+    storageState: {
+      origins: [
+        {
+          localStorage: [{ name: 'HOLI_TRACKING_CONSENT', value: '{"ANALYTICS":"denied","PERSONALIZATION":"denied"}' }],
+          origin: baseURL,
+        },
+      ],
+      cookies: [],
+    },
   },
 
   projects: browsersFromEnv(process.env.E2E_WEB_BROWSERS),
diff --git a/e2e/web/tests/forgotPassword.spec.ts b/e2e/web/tests/forgotPassword.spec.ts
index f29b780b43..f4b5e231ea 100644
--- a/e2e/web/tests/forgotPassword.spec.ts
+++ b/e2e/web/tests/forgotPassword.spec.ts
@@ -1,17 +1,9 @@
-import {
-  byTestId,
-  checkAccessibility,
-  clickElementByTestId,
-  clickFirstElement,
-  dismissConsentDialog,
-  locateElementByTestId,
-} from './helpers'
+import { byTestId, checkAccessibility, clickElementByTestId, clickFirstElement, locateElementByTestId } from './helpers'
 import { test } from './testmaster'
 
 test.describe('@ForgotPassword', () => {
   test('user journey', async ({ page }) => {
     await page.goto('/')
-    await dismissConsentDialog(page)
 
     await clickElementByTestId(page, 'btn-to-onboarding')
 
diff --git a/e2e/web/tests/helpers.ts b/e2e/web/tests/helpers.ts
index b0c3a97b4b..480e3ba488 100644
--- a/e2e/web/tests/helpers.ts
+++ b/e2e/web/tests/helpers.ts
@@ -5,8 +5,8 @@ import { checkA11y, configureAxe, injectAxe } from 'axe-playwright'
 import { deleteAuthenticatedAccount } from '@holi/e2e-web/tests/helpers/api'
 import { newUserEmail, newUserPassword, randomPassword } from '@holi/e2e-web/tests/helpers/fixtures'
 
-import { test } from './testmaster'
 import type { OnboardingUserData } from '@holi/core/screens/onboarding/types'
+import { test } from './testmaster'
 
 // sometimes requests to the app backends take a little longer (10s < response latency < 40s)
 export const donationsRequestTimeout = 40_000
diff --git a/e2e/web/tests/helpers/api.ts b/e2e/web/tests/helpers/api.ts
index 958ea2d7c5..5b7926d700 100644
--- a/e2e/web/tests/helpers/api.ts
+++ b/e2e/web/tests/helpers/api.ts
@@ -3,6 +3,8 @@ import { APIRequestContext, BrowserContext } from '@playwright/test'
 
 import { newUserEmail, randomPassword } from './fixtures'
 
+import { readFileSync } from 'fs'
+
 export type User = {
   id: string
   identity: string
@@ -123,3 +125,89 @@ export const signUp = async (
     password,
   }
 }
+
+export type E2ETestSpaceResponse = {
+  id: string
+  title: string
+  description: string
+  name: string
+  cover: string
+  coverBlurhash: string
+}
+
+export const createSpace = async (context: BrowserContext) => {
+  const file = new File([readFileSync('tests/helpers/testimage.png')], 'testimage.png')
+
+  const formData = new FormData()
+  formData.append(
+    'operations',
+    JSON.stringify({
+      operationName: 'CreateSpace',
+      variables: {
+        input: {
+          title: `E2E ${faker.company.name()}`,
+          description: 'E2E Space Description',
+          topics: ['e3f03ff4-b205-4196-9eff-57b80e5b0fff'], //Other
+          cover: null,
+        },
+      },
+      query:
+        'mutation CreateSpace($input: CreateSpaceInput!) {\ncreateSpace(input: $input) {\n id\n title\n description\n name\n cover\n coverBlurhash\n name\n}\n}',
+    })
+  )
+  formData.append('map', '{"1":["variables.input.cover"]}')
+  formData.append('1', file, 'testimage.png')
+
+  // todo gregor: use codegen types after generating the create space types
+  // @ts-ignore
+  const createSpaceResponse: Response<{ data?: { createSpace: E2ETestSpaceResponse }; errors?: object[] }> = await (
+    await context.request.post(`${baseURL}/api/graphql`, {
+      multipart: formData,
+    })
+  ).json()
+
+  if (createSpaceResponse.errors) {
+    throw new Error('Failed to create space ' + JSON.stringify(createSpaceResponse.errors, null, 2))
+  }
+
+  return createSpaceResponse.data.createSpace
+}
+
+export type E2ETestSpaceTaskResponse = {
+  id: string
+  name: string
+  description: string
+}
+
+export const createSpaceTask = async (context: BrowserContext, spaceId: string) => {
+  // todo gregor: use codegen types after generating the create space task types
+  // @ts-ignore
+  const createSpaceTaskResponse: { data?: { createTask: E2ETestSpaceTaskResponse }; errors?: object[] } = await (
+    await context.request.post(`${baseURL}/api/graphql`, {
+      data: {
+        operationName: 'CreateTask',
+        query:
+          'mutation CreateTask($input: CreateTaskInput!) {  createTask(input: $input) {    id    name    description    visibility    regularity    locationType    location    contactEmail    contactPhone    __typename  }}',
+        variables: {
+          input: {
+            spaceId: spaceId,
+            name: faker.lorem.words(3),
+            description: faker.lorem.lines(3),
+            visibility: 'PUBLIC',
+            locationType: 'REMOTE',
+            regularity: 'ONE_TIME',
+            contactEmail: faker.internet.email(),
+            slotsAvailable: 1,
+            skills: [],
+          },
+        },
+      },
+    })
+  ).json()
+
+  if (createSpaceTaskResponse.errors) {
+    throw new Error('Failed to create space task ' + JSON.stringify(createSpaceTaskResponse.errors, null, 2))
+  }
+
+  return createSpaceTaskResponse.data!.createTask
+}
diff --git a/e2e/web/tests/helpers/testimage.png b/e2e/web/tests/helpers/testimage.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f77ef3cc0ad9bf311a8de4ab78ced8f8f720aad
GIT binary patch
literal 619
zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k2}mkgS)K$^Ea{HEjtmSN`?>!lvNA9*DS5g$
zhE&A8z0e4hWH`LwXR!Q})_}wV4UEieJQ4;8ql}Rq2p<d#u1;rQWK9=Z3rZlKu6{1-
HoD!M<Wo(TF

literal 0
HcmV?d00001

diff --git a/e2e/web/tests/taskCollaboration.spec.ts b/e2e/web/tests/taskCollaboration.spec.ts
new file mode 100644
index 0000000000..06b9d24add
--- /dev/null
+++ b/e2e/web/tests/taskCollaboration.spec.ts
@@ -0,0 +1,67 @@
+import { clickButtonByText, clickElementByTestId, locateElementByTestId, retry } from '@holi/e2e-web/tests/helpers'
+import { test } from '@holi/e2e-web/tests/testmaster'
+import { expect } from '@playwright/test'
+
+test.describe('@Task collaboration multi user journey', () => {
+  test('Task Space Admins can be contacted by User', async ({ sessions }) => {
+    const { page: alice, user: aliceUser, api: aliceApi } = await sessions.signUp()
+    const aliceFullName = aliceUser.firstName + ' ' + aliceUser.lastName
+    const { page: bob, user: bobUser } = await sessions.signUp()
+    const bobFullName = bobUser.firstName + ' ' + bobUser.lastName
+    const { page: carol } = await sessions.signUp()
+
+    const space = await aliceApi.createSpace()
+    const task = await aliceApi.createSpaceTask(space.id)
+
+    // wait for the home to stabilize
+    await expect(alice.getByTestId('home-feed-insights-heading')).toBeVisible()
+
+    await test.step('Another User is promoted to space admin', async () => {
+      //A invites B to space
+      await alice.goto('/spaces/' + space.name)
+      await clickElementByTestId(alice, 'btn-space-invite')
+      const messageTextField = await locateElementByTestId(alice, 'space-invite-user-input')
+      await messageTextField.fill(bobFullName)
+
+      await clickElementByTestId(alice, 'btn-to-invite')
+
+      // B accepts the invite notification
+      await retry(async () => {
+        await bob.goto('/notifications')
+        await expect(
+          await locateElementByTestId(bob, `space-invite-notification-item_${bobUser.id}_${aliceUser.id}`)
+        ).toBeVisible()
+      })
+      await clickElementByTestId(bob, 'accept-invite-button')
+
+      //make B admin
+      await alice.goto('/spaces/' + space.name)
+      await clickElementByTestId(alice, 'btn-to-open-space-collaborators-modal')
+      await clickButtonByText(alice, bobFullName)
+      await clickElementByTestId(alice, 'spaces-collaborators-grant-admin-btn')
+      await clickElementByTestId(alice, 'spaces-collaborators-grant-admin-confirmation-confirmation-primary')
+    })
+
+    await test.step('User can engage in contact', async () => {
+      await carol.goto('/spaces/' + space.id + '/tasks')
+      await carol.getByTestId('link').first().click()
+      await carol.getByLabel('Contact organization').click()
+    })
+
+    await test.step('Task chat explanation modal is confirmable', async () => {
+      await expect(carol.getByText('Start by simply saying "Hi"')).toBeVisible()
+      await carol.getByRole('button', { name: 'Start chatting' }).click()
+    })
+
+    await test.step('Multi user chat room with all space admins opens', async () => {
+      await expect(async () => {
+        carol.getByRole('banner', { name: task.name })
+        carol.getByRole('button', { name: 'Send message' })
+      }).toPass()
+
+      //and all admins are mentioned
+      await expect(carol.getByRole('link', { name: aliceFullName })).toBeVisible()
+      await expect(carol.getByRole('link', { name: bobFullName })).toBeVisible()
+    })
+  })
+})
diff --git a/e2e/web/tests/testmaster.ts b/e2e/web/tests/testmaster.ts
index 898ff50477..74bcc6d1d1 100644
--- a/e2e/web/tests/testmaster.ts
+++ b/e2e/web/tests/testmaster.ts
@@ -1,14 +1,25 @@
 import { type Browser, type BrowserContext, type Page, test as baseTest } from '@playwright/test'
 
+import { OnboardingUserData } from '@holi/core/screens/onboarding/types'
 import { gotoWithRetries } from './helpers'
-import { denyConsent, withRandomUserSession } from './helpers'
-import { type User, deleteAuthenticatedAccount, signUp } from './helpers/api'
-import type { OnboardingUserData } from '@holi/core/screens/onboarding/types'
+import { withRandomUserSession } from './helpers'
+import {
+  E2ETestSpaceResponse,
+  E2ETestSpaceTaskResponse,
+  type User,
+  createSpace,
+  createSpaceTask,
+  deleteAuthenticatedAccount,
+  signUp,
+} from './helpers/api'
 
 type Session = {
   page: Page
   user: User
-  //api: { createSpace: () => void }
+  api: {
+    createSpace: () => Promise<E2ETestSpaceResponse>
+    createSpaceTask: (spaceId: string) => Promise<E2ETestSpaceTaskResponse>
+  }
 }
 
 export class UserHandler {
@@ -72,15 +83,17 @@ export class SessionHandler {
       // If the initial attempt fails, fall back to retry logic
       await gotoWithRetries(newPage, '/')
     }
-    await denyConsent(newPage)
+    //await denyConsent(newPage)
 
     return {
       user,
+      // todo gregor: rename to newBrowserPage to communicate its not just a new tab based b/c newContext above
       page: newPage,
       // TODO: expand to enable setting up data for test runs, without having to drive the UI (slow)
-      // api: {
-      //   createSpace: () => {//make it so via GQL calls},
-      // },
+      api: {
+        createSpace: () => createSpace(context),
+        createSpaceTask: (spaceId: string) => createSpaceTask(context, spaceId),
+      },
     }
   }
 
diff --git a/holi-bricks/components/avatar/AvatarPile.tsx b/holi-bricks/components/avatar/AvatarPile.tsx
new file mode 100644
index 0000000000..2edae4fbb7
--- /dev/null
+++ b/holi-bricks/components/avatar/AvatarPile.tsx
@@ -0,0 +1,59 @@
+import type React from 'react'
+import { View } from 'react-native'
+
+import { Avatar, type AvatarProps } from 'holi-bricks/components/avatar'
+import { AvatarSizesValues } from 'holi-bricks/components/avatar/Avatar'
+import { useStyles } from 'holi-bricks/hooks'
+import { createStyleSheet } from 'holi-bricks/utils'
+// eslint-disable-next-line no-restricted-imports
+import type { User } from '@holi/core/domain/shared/types'
+
+interface AvatarPileProps extends Omit<AvatarProps, 'initials'> {
+  users: User[]
+}
+
+const AvatarPile = ({
+  users,
+  size = 'md',
+  testID = 'holi-user-avatar-pile',
+  border,
+  shape,
+}: React.PropsWithChildren<AvatarPileProps>) => {
+  const { styles } = useStyles(stylesheet)
+  return (
+    <View style={styles.container} testID={testID}>
+      {users?.map((user, idx) => {
+        const overlapDistance = AvatarSizesValues[size] / -2.4
+        return (
+          <View style={styles.overlap(idx, overlapDistance)} key={idx}>
+            <Avatar
+              border={border}
+              shape={shape}
+              size={size}
+              label={user.fullName}
+              imgSrc={user.avatar}
+              initials={user.avatarLabel}
+              testID="pile-avatar"
+              imgPlaceholder={user.avatarBlurhash}
+            />
+          </View>
+        )
+      })}
+    </View>
+  )
+}
+
+export default AvatarPile
+
+const stylesheet = createStyleSheet(() => ({
+  container: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  overlap: (idx, overlapDistance) => {
+    return {
+      marginLeft: idx ? overlapDistance : 0,
+      zIndex: idx * -1,
+    }
+  },
+}))
diff --git a/packages/api/graphql/graphql-codegen.ts b/packages/api/graphql/graphql-codegen.ts
index fc228d12e9..9febf77551 100644
--- a/packages/api/graphql/graphql-codegen.ts
+++ b/packages/api/graphql/graphql-codegen.ts
@@ -2446,6 +2446,7 @@ export type TaskByIdQuery = {
       id: string
       connectionStatusToMyself: Array<SpaceUserConnectionType>
       name: string
+      title: string
       avatar?: string
       avatarBlurhash?: string
       topics: Array<{ __typename?: 'Topic'; id: string; title: string }>
@@ -2659,6 +2660,7 @@ export const TaskByIdDocument = gql`
       id
       connectionStatusToMyself
       name
+      title
       avatar
       avatarBlurhash
       topics {
diff --git a/packages/chat/src/client/ChatClient.ts b/packages/chat/src/client/ChatClient.ts
index a7b72a156d..fd47066314 100644
--- a/packages/chat/src/client/ChatClient.ts
+++ b/packages/chat/src/client/ChatClient.ts
@@ -9,14 +9,14 @@ import {
   type ICreateClientOpts,
   type ICreateRoomOpts,
   type IStartClientOpts,
-  MatrixClient,
+  type MatrixClient,
   type MatrixEvent,
   MatrixScheduler,
   Preset,
   PushRuleKind,
-  Room,
+  type Room,
   RoomEvent,
-  RoomMember,
+  type RoomMember,
   type RoomState,
   RoomStateEvent,
   Visibility,
@@ -25,7 +25,7 @@ import {
 import { ReceiptType } from 'matrix-js-sdk/lib/@types/read_receipts'
 import { logger as matrixLogger } from 'matrix-js-sdk/lib/logger'
 import { SyncState } from 'matrix-js-sdk/lib/sync'
-import { MsgType, RoomMessageEventContent } from 'matrix-js-sdk/lib/types'
+import type { MsgType, RoomMessageEventContent } from 'matrix-js-sdk/lib/types'
 
 import { configurePusher } from '@holi/chat/src/client/pusher'
 import {
@@ -33,14 +33,16 @@ import {
   ChatRoomEvent,
   type ChatRoomMember,
   type ChatRoomMessage,
+  type ChatRoomType,
   type CreateRoomOptions,
   type MatrixId,
   MembershipStatus,
   type MxMessagesResponse,
   type NotificationCount,
+  type RoomHoliMetadata,
   type RoomId,
   type RoomUpdatesContent,
-  RoomUpdatesType,
+  type RoomUpdatesType,
   type RoomWithSharedData,
 } from '@holi/chat/src/client/types'
 import {
@@ -62,7 +64,7 @@ import {
   setRoomsListeners,
 } from '@holi/chat/src/client/utils'
 import { isEmptyString, isString } from '@holi/chat/src/utils'
-import { type ChatCredentials } from '@holi/chat/src/utils/types'
+import type { ChatCredentials } from '@holi/chat/src/utils/types'
 import { HoliChatClientError } from '@holi/core/errors/classes/HoliChatClientError'
 import { logError } from '@holi/core/errors/helpers'
 import { environment } from '@holi/core/helpers/environment'
@@ -102,6 +104,13 @@ export default class ChatClient {
     return { sharedDataEvent, parentEvent }
   }
 
+  public getRoomHoliMetadata(room: Room): RoomHoliMetadata | undefined {
+    const state = room.getLiveTimeline().getState(Direction.Forward)
+    const roomCreateEvent = state?.getStateEvents(EventType.RoomCreate)?.pop()
+
+    return roomCreateEvent?.getContent() as RoomHoliMetadata | undefined
+  }
+
   private getSpaceChildEvents(): MatrixEvent[] {
     return this.mxClient.getRooms().reduce((spaceEvents: MatrixEvent[], room: Room) => {
       if (!room.isSpaceRoom()) return spaceEvents
@@ -116,10 +125,27 @@ export default class ChatClient {
 
   private createSharedData(room: Room): RoomWithSharedData {
     const { sharedDataEvent, parentEvent } = this.getSpaceEvents(room)
-    if (!sharedDataEvent) return { type: 'private', room }
 
+    // Check type from `RoomCreate` event
+    if (!sharedDataEvent) {
+      const roomHoliMetadata = this.getRoomHoliMetadata(room)
+
+      switch (roomHoliMetadata?.type) {
+        case 'space.task':
+          return {
+            type: roomHoliMetadata.type,
+            content: roomHoliMetadata.content,
+            room,
+          }
+        default:
+          return { type: 'private', room }
+      }
+    }
+
+    // Chat type based on our custom `SharedData` event
     const content = sharedDataEvent.getEffectiveEvent().content
 
+    // Space Parent Room Shared Data
     if (room.isSpaceRoom() && isSharedDataContent(content)) {
       return { type: 'space', room, content }
     }
@@ -260,20 +286,32 @@ export default class ChatClient {
     return this.mxClient.getHomeserverUrl()
   }
 
-  /** Rooms */
-  public async getOrCreateRoom(userIds: string[], options: CreateRoomOptions): Promise<RoomId> {
+  private getRoomByUserIds(matrixUserIds: string[]): Room | undefined {
     const room = this.mxClient.getRooms().find((room) => {
+      const members = room.getMembers()
+
       return (
-        // look for a room that has all the requested userIds and is not a space room
         !room.isSpaceRoom() &&
-        room.getMembers().length == userIds.length + 1 &&
-        room.getMembers().filter((roomMember) => userIds.includes(roomMember.userId)).length == userIds.length
+        members.length == matrixUserIds.length + 1 && // +1 is for the logged in user
+        matrixUserIds.every((id) => members.find((member) => member.userId === id))
       )
     })
 
-    if (room) return { room_id: room.roomId }
+    return room
+  }
+
+  public getExistingRoomId(userIds: string[], roomType: ChatRoomType = 'private'): string | undefined {
+    const room = this.getRoomByUserIds(userIds)
+    const roomMetadata = room && this.getRoomHoliMetadata(room)
+    return roomMetadata?.type === roomType ? room?.roomId : undefined
+  }
 
-    return this.createRoom(userIds, options)
+  /**
+   * Rooms
+   * */
+  public async getOrCreateRoom(userIds: string[], options: CreateRoomOptions): Promise<RoomId> {
+    const room_id = this.getExistingRoomId(userIds, options.metadata.type ?? 'private')
+    return room_id ? { room_id } : this.createRoom(userIds, options)
   }
 
   /**
@@ -290,7 +328,7 @@ export default class ChatClient {
    **/
   public async createRoom(
     userIds: string[],
-    { isDirectMessage, isPrivate = true, name }: CreateRoomOptions
+    { isDirectMessage, isPrivate = true, name, metadata }: CreateRoomOptions
   ): Promise<RoomId> {
     if (userIds.length < 1) {
       throw new HoliChatClientError('createRoom', 'Chat rooms must be created with at least one member')
@@ -300,6 +338,7 @@ export default class ChatClient {
       is_direct: isDirectMessage,
       preset: isPrivate ? Preset.PrivateChat : Preset.PublicChat,
       visibility: isPrivate ? Visibility.Private : Visibility.Public,
+      creation_content: metadata,
       ...(name ? { name } : {}),
     }
 
@@ -485,6 +524,23 @@ export default class ChatClient {
     }
   }
 
+  public subscribeToRoomCreateEvent(callback: (chatRoom: ChatRoom) => void): () => void {
+    const handleRoomCreated = (event: MatrixEvent, roomState: RoomState) => {
+      if (event.getType() !== EventType.RoomCreate) return
+
+      const room = this.mxClient.getRoom(roomState.roomId)
+      if (!room) return
+
+      callback(this.createChatRoom(room))
+    }
+
+    this.mxClient.on(RoomStateEvent.Events, handleRoomCreated)
+
+    return () => {
+      this.mxClient.off(RoomStateEvent.Events, handleRoomCreated)
+    }
+  }
+
   public subscribeToRoomsUpdates(
     callback: (roomId: string, type: RoomUpdatesType, content: RoomUpdatesContent) => void
   ): () => void {
diff --git a/packages/chat/src/client/__tests__/ChatClient.createRoom.test.ts b/packages/chat/src/client/__tests__/ChatClient.createRoom.test.ts
index fbe3d29f16..661231e83e 100644
--- a/packages/chat/src/client/__tests__/ChatClient.createRoom.test.ts
+++ b/packages/chat/src/client/__tests__/ChatClient.createRoom.test.ts
@@ -1,17 +1,17 @@
 import {
   EventType,
-  IEvent,
-  IRoomEvent,
-  IStateEvent,
-  MatrixClient,
-  MatrixEvent,
+  type IEvent,
+  type IRoomEvent,
+  type IStateEvent,
+  type MatrixClient,
+  type MatrixEvent,
   Preset,
   ReceiptType,
   Visibility,
 } from 'matrix-js-sdk'
 
-import ChatClient from '@holi/chat/src/client/ChatClient'
-import { ChatRoomEvent, MatrixId, MembershipStatus } from '@holi/chat/src/client/types'
+import type ChatClient from '@holi/chat/src/client/ChatClient'
+import { ChatRoomEvent, type MatrixId, MembershipStatus } from '@holi/chat/src/client/types'
 import {
   DM_ROOM_ID,
   createChatClient,
@@ -42,7 +42,7 @@ describe('ChatClient', () => {
 
       const isDM = true
 
-      const { room_id } = await client.createRoom(members, { isDirectMessage: isDM })
+      const { room_id } = await client.createRoom(members, { isDirectMessage: isDM, metadata: { type: 'private' } })
 
       expect(room_id).toEqual(DM_ROOM_ID)
       expect(mxClient.createRoom).toHaveBeenCalledTimes(1)
@@ -51,6 +51,9 @@ describe('ChatClient', () => {
         is_direct: isDM,
         preset: 'private_chat',
         visibility: 'private',
+        creation_content: {
+          type: 'private',
+        },
       })
     })
 
@@ -60,7 +63,7 @@ describe('ChatClient', () => {
       const isDirectMessage = false
       const name = '#general'
 
-      const { room_id } = await client.createRoom(members, { isDirectMessage, name })
+      const { room_id } = await client.createRoom(members, { isDirectMessage, name, metadata: { type: 'private' } })
       expect(room_id).toEqual(DM_ROOM_ID)
       expect(mxClient.createRoom).toHaveBeenCalledTimes(1)
       expect(mxClient.createRoom).toHaveBeenLastCalledWith({
@@ -69,6 +72,9 @@ describe('ChatClient', () => {
         preset: Preset.PrivateChat,
         visibility: Visibility.Private,
         name,
+        creation_content: {
+          type: 'private',
+        },
       })
     })
 
@@ -78,14 +84,16 @@ describe('ChatClient', () => {
       const members: string[] = []
       const isDirectMessage = true
 
-      expect(client.createRoom(members, { isDirectMessage })).rejects.toThrowError(HoliChatClientError)
+      expect(client.createRoom(members, { isDirectMessage, metadata: { type: 'private' } })).rejects.toThrowError(
+        HoliChatClientError
+      )
     })
   })
 
   describe('getOrCreateRoom() method', () => {
     describe('creates a new room if', () => {
       const checkIfANewRoomWasCreated = async () => {
-        await client.getOrCreateRoom(members, { isDirectMessage: true })
+        await client.getOrCreateRoom(members, { isDirectMessage: true, metadata: { type: 'private' } })
 
         expect(mxClient.createRoom).toHaveBeenCalled()
       }
@@ -126,10 +134,18 @@ describe('ChatClient', () => {
     it('finds an existing room and returns it.', async () => {
       const roomMemberMocks = members.map((userId) => createRoomMemberMock(userId))
       const roomId = 'testRoomId'
-      const roomMock = createRoomMock(roomId, { memberMocks: roomMemberMocks })
+      const createEvent = createMatrixEventMock(EventType.RoomCreate, roomId, { type: 'private' })
+      const roomMock = createRoomMock(roomId, {
+        memberMocks: roomMemberMocks,
+        timelineEvents: [],
+        stateEvents: [createEvent],
+      })
       mxClient.getRooms.mockReturnValue([roomMock])
 
-      const { room_id } = await client.getOrCreateRoom([members[0]], { isDirectMessage: true })
+      const { room_id } = await client.getOrCreateRoom([members[0]], {
+        isDirectMessage: true,
+        metadata: { type: 'private' },
+      })
       expect(room_id).toEqual(roomId)
       expect(mxClient.createRoom).not.toHaveBeenCalled()
     })
diff --git a/packages/chat/src/client/types.ts b/packages/chat/src/client/types.ts
index e8382c45b7..a41a63573d 100644
--- a/packages/chat/src/client/types.ts
+++ b/packages/chat/src/client/types.ts
@@ -1,9 +1,7 @@
-import { EventType, IRoomEvent, IStateEvent, NotificationCountType, Room } from 'matrix-js-sdk'
+import { EventType, type IRoomEvent, type IStateEvent, type NotificationCountType, type Room } from 'matrix-js-sdk'
 
 export type MatrixId = `@${string}:${string}`
 
-export type ChatRoomType = 'private' | 'space' | 'space.child'
-
 export type SharedDataContent = SpaceSharedDataContent | SpaceChildSharedDataContent
 
 export interface SpaceSharedDataContent {
@@ -17,24 +15,39 @@ export interface SpaceChildSharedDataContent extends SpaceSharedDataContent {
   parentSpaceRoomId: string
 }
 
+export interface SpaceTaskSharedDataContent {
+  taskName: string
+  taskId: string
+  spaceName: string
+  holiSpaceId: string
+}
+
 export interface PrivateRoom {
   type: 'private'
-  room: Room
 }
 
 export interface SpaceRoom {
   type: 'space'
   content: SpaceSharedDataContent
-  room: Room
 }
 
 export interface SpaceChildRoom {
   type: 'space.child'
   content: SpaceChildSharedDataContent
-  room: Room
 }
 
-export type RoomWithSharedData = PrivateRoom | SpaceRoom | SpaceChildRoom
+export interface SpaceTaskRoom {
+  type: 'space.task'
+  content: SpaceTaskSharedDataContent
+}
+
+type AddRoom<T> = {
+  [K in keyof T]: T[K]
+} & { room: Room }
+
+export type RoomHoliMetadata = PrivateRoom | SpaceRoom | SpaceChildRoom | SpaceTaskRoom
+export type RoomWithSharedData = AddRoom<RoomHoliMetadata>
+export type ChatRoomType = RoomWithSharedData['type']
 
 export interface BaseChatRoom {
   id: string
@@ -57,7 +70,11 @@ export interface SpaceChildChatRoom extends BaseChatRoom, Omit<SpaceChildRoom, '
   content: SpaceChildSharedDataContent
 }
 
-export type ChatRoom = PrivateChatRoom | SpaceChatRoom | SpaceChildChatRoom
+export interface SpaceTaskChatRoom extends BaseChatRoom, Omit<SpaceTaskRoom, 'room'> {
+  content: SpaceTaskSharedDataContent
+}
+
+export type ChatRoom = PrivateChatRoom | SpaceChatRoom | SpaceChildChatRoom | SpaceTaskChatRoom
 
 export enum RoomUpdatesType {
   name = EventType.RoomName,
@@ -73,6 +90,7 @@ export interface CreateRoomOptions {
   isDirectMessage: boolean
   isPrivate?: boolean
   name?: string
+  metadata: RoomHoliMetadata
 }
 
 export enum MembershipStatus {
diff --git a/packages/chat/src/client/utils.ts b/packages/chat/src/client/utils.ts
index 0245d3f6ac..ad749023ac 100644
--- a/packages/chat/src/client/utils.ts
+++ b/packages/chat/src/client/utils.ts
@@ -1,4 +1,4 @@
-import { TFunction } from 'i18next'
+import type { TFunction } from 'i18next'
 import {
   ClientEvent,
   EventType,
@@ -9,11 +9,11 @@ import {
   type IStateEventWithRoomId,
   type Listener,
   type ListenerMap,
-  MatrixClient,
-  MatrixEvent,
+  type MatrixClient,
+  type MatrixEvent,
   type Room,
-  RoomEvent,
-  RoomMember,
+  type RoomEvent,
+  type RoomMember,
   type RoomNameState,
   RoomNameType,
 } from 'matrix-js-sdk'
@@ -39,6 +39,8 @@ import {
   type SpaceChildRoom,
   type SpaceChildSharedDataContent,
   type SpaceRoom,
+  SpaceTaskChatRoom,
+  SpaceTaskRoom,
 } from '@holi/chat/src/client/types'
 import { getChatMemberName } from '@holi/chat/src/utils'
 import { HoliError } from '@holi/core/errors/classes/HoliError'
@@ -183,8 +185,10 @@ export const createChatRoom = (
       return createSpaceChatRoom(baseChatRoom, roomWithSharedData)
     case 'space.child':
       return createSpaceChildChatRoom(baseChatRoom, roomWithSharedData)
+    case 'space.task':
+      return createSpaceTaskChatRoom(baseChatRoom, roomWithSharedData)
     default:
-      throw new HoliError('Unexpected ChatRoom: private, space and space.child supported')
+      throw new HoliError('Unexpected ChatRoom: private, space, space.child and space.task supported')
   }
 }
 
@@ -223,9 +227,17 @@ export function createSpaceChildChatRoom(
   }
 }
 
+export function createSpaceTaskChatRoom(baseChatRoom: BaseChatRoom, spaceTaskRoom: SpaceTaskRoom): SpaceTaskChatRoom {
+  return {
+    type: spaceTaskRoom.type,
+    content: { ...spaceTaskRoom.content },
+    ...baseChatRoom,
+  }
+}
+
 /**
  * @description It creates the base for all 3 (internal) types of chats rooms
- * `PrivateChatRoom`, `SpaceChatRoom`, `SpaceChildChatRoom`
+ * `PrivateChatRoom`, `SpaceChatRoom`, `SpaceChildChatRoom`, `SpaceTaskChatRoom`
  */
 export function createBaseChatRoom(
   room: Room,
diff --git a/packages/chat/src/components/ChatProfileCard.tsx b/packages/chat/src/components/ChatProfileCard.tsx
index 1d5c4e2451..207709ae02 100644
--- a/packages/chat/src/components/ChatProfileCard.tsx
+++ b/packages/chat/src/components/ChatProfileCard.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import type { PropsWithChildren } from 'react'
-import { useTranslation } from 'react-i18next'
+import { Trans, useTranslation } from 'react-i18next'
 import { StyleSheet, View } from 'react-native'
 
 import type { ChatRoomMember } from '@holi/chat/src/client/types'
@@ -9,24 +9,23 @@ import { getChatInitials } from '@holi/chat/src/utils'
 import type { User } from '@holi/core/domain/shared/types'
 import useRouting from '@holi/core/navigation/hooks/useRouting'
 import { HoliDivider } from '@holi/ui/components/atoms/HoliDivider'
-import { HoliHeading } from '@holi/ui/components/atoms/HoliHeading'
-import HoliText from '@holi/ui/components/atoms/HoliText'
 import { Button } from 'holi-bricks/components/button'
 import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
 import HoliCardBoard from '@holi/ui/components/organisms/HoliCardBoard'
 import { dimensions } from '@holi/ui/styles/globalVars'
 import { Avatar } from 'holi-bricks/components/avatar'
 import { Spacing } from 'holi-bricks/tokens'
+import { Text } from 'holi-bricks/components/text'
 
 interface Props {
   roomId: string
   member: ChatRoomMember | undefined
   user?: User
-  userIsLoading: boolean
+  taskName?: string
 }
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
-const ChatProfileCard = ({ member, roomId, user, userIsLoading }: PropsWithChildren<Props>) => {
+const ChatProfileCard = ({ member, roomId, user, taskName }: PropsWithChildren<Props>) => {
   const { t } = useTranslation()
   const { navigate } = useRouting()
   const { openToast } = useToast()
@@ -59,16 +58,35 @@ const ChatProfileCard = ({ member, roomId, user, userIsLoading }: PropsWithChild
                 initials={getChatInitials(member?.name) || user?.avatarLabel || ''}
                 size="lg"
               />
+
               <View>
-                <HoliHeading size="m">{chatPartnerName}</HoliHeading>
-                {!!user?.pronouns && <HoliText color="gray300">{user?.pronouns}</HoliText>}
+                <Text size="xxl" headingLevel="1">
+                  {chatPartnerName}
+                </Text>
+
+                {!!user?.pronouns && (
+                  <Text color="default" size="md">
+                    {user?.pronouns}
+                  </Text>
+                )}
               </View>
             </>
           ) : null}
         </HoliCardBoard.Slot>
         <HoliDivider />
         <HoliCardBoard.Slot>
-          <HoliText color="gray300">{t('chat.invitation.copy', { fullName: chatPartnerName })}</HoliText>
+          {taskName ? (
+            <Text size="md" color="default">
+              <Trans t={t} i18nKey={'chat.invitation.copy.taskChat'} values={{ fullName: chatPartnerName, taskName }}>
+                <Text size="md" color="informative" />
+              </Trans>
+            </Text>
+          ) : (
+            <Text size="md" color="default">
+              {t('chat.invitation.copy', { fullName: chatPartnerName })}
+            </Text>
+          )}
+
           <View style={styles.buttons}>
             <Button inline label={t('chat.invitation.reject')} variant="secondary" onPress={handleRejectRoom} />
             <Button inline label={t('chat.invitation.accept')} testID={'chat-invitation-accept'} onPress={handleJoin} />
diff --git a/packages/chat/src/components/ChatRoomActionDrawer.tsx b/packages/chat/src/components/ChatRoomActionDrawer.tsx
index b57cf4aef2..75ee8c413b 100644
--- a/packages/chat/src/components/ChatRoomActionDrawer.tsx
+++ b/packages/chat/src/components/ChatRoomActionDrawer.tsx
@@ -1,21 +1,21 @@
 import React from 'react'
 import { useTranslation } from 'react-i18next'
 
-import type { ChatRoomMember } from '@holi/chat/src/client/types'
 import { ignoreUser, leaveRoom, useSetRemoveRoomAtom } from '@holi/chat/src/store'
 import useRouting from '@holi/core/navigation/hooks/useRouting'
 import { CloseCircle, Delete } from '@holi/icons/src/generated'
 import { HoliToastType, useToast } from '@holi/ui/components/molecules/HoliToastProvider'
 import HoliActionDrawer from '@holi/ui/components/organisms/HoliActionDrawer'
 import { useTheme } from '@holi/ui/styles/theme'
+import type { User } from '@holi/core/domain/shared/types'
 
 interface Props {
-  chatPartner: ChatRoomMember
   chatPartnerFullName: string
   roomId: string
+  usersToBlock: User[]
 }
 
-const ChatRoomActionDrawer = ({ chatPartner, chatPartnerFullName, roomId }: Props) => {
+const ChatRoomActionDrawer = ({ chatPartnerFullName, roomId, usersToBlock }: Props) => {
   const { t } = useTranslation()
   const { navigate } = useRouting()
   const { openToast } = useToast()
@@ -23,11 +23,11 @@ const ChatRoomActionDrawer = ({ chatPartner, chatPartnerFullName, roomId }: Prop
   const { theme } = useTheme()
   const { colors } = theme
 
-  const handleBlockConfirmation = async () => {
-    await ignoreUser(chatPartner.id)
+  const handleBlockConfirmation = async (user: User) => {
+    await ignoreUser(user.id)
     navigate('/chat')
     removeRoom(roomId)
-    openToast(t('user.profileView.block.successMessage', { name: chatPartnerFullName }), HoliToastType.SUCCESS)
+    openToast(t('user.profileView.block.successMessage', { name: user.fullName }), HoliToastType.SUCCESS)
   }
 
   const handleLeaveConfirmation = async () => {
@@ -38,28 +38,30 @@ const ChatRoomActionDrawer = ({ chatPartner, chatPartnerFullName, roomId }: Prop
 
   return (
     <HoliActionDrawer.Drawer label={t('chat.actions.more')} testID="chat-room-action-drawer">
-      <HoliActionDrawer.Action
-        icon={CloseCircle}
-        title={t('chat.actions.block', { name: chatPartnerFullName })}
-        iconColor={colors.error20}
-        variant="danger"
-        showConfirmation
-      >
-        <HoliActionDrawer.Confirmation
-          confirmText={t('chat.actions.block.confirmation.block')}
-          dismissText={t('chat.actions.block.confirmation.cancel')}
-          title={t('chat.actions.block', { name: chatPartnerFullName })}
-          description={t('chat.actions.block.confirmation.text', { name: chatPartnerFullName })}
-          testID="chat-room-block-confirmation"
-          handleConfirmation={handleBlockConfirmation}
-          hasDismissBtn
-          requireLogin
-        />
-      </HoliActionDrawer.Action>
-
+      {usersToBlock?.map((user) => (
+        <HoliActionDrawer.Action
+          icon={CloseCircle}
+          title={t('chat.actions.block', { name: user.fullName })}
+          iconColor={colors.error20}
+          variant="danger"
+          showConfirmation
+          key={user.fullName}
+        >
+          <HoliActionDrawer.Confirmation
+            confirmText={t('chat.actions.block.confirmation.block')}
+            dismissText={t('chat.actions.block.confirmation.cancel')}
+            title={t('chat.actions.block', { name: user.fullName })}
+            description={t('chat.actions.block.confirmation.text', { name: user.fullName })}
+            testID="chat-room-block-confirmation"
+            handleConfirmation={() => handleBlockConfirmation(user)}
+            hasDismissBtn
+            requireLogin
+          />
+        </HoliActionDrawer.Action>
+      ))}
       <HoliActionDrawer.Action
         icon={Delete}
-        title={t('chat.actions.leave', { name: chatPartnerFullName })}
+        title={t('chat.actions.leave')}
         iconColor={colors.error20}
         variant="danger"
         showConfirmation
@@ -67,10 +69,10 @@ const ChatRoomActionDrawer = ({ chatPartner, chatPartnerFullName, roomId }: Prop
         <HoliActionDrawer.Confirmation
           confirmText={t('chat.actions.leave.confirmation.leave')}
           dismissText={t('chat.actions.leave.confirmation.cancel')}
-          title={t('chat.actions.leave', { name: chatPartnerFullName })}
+          title={t('chat.actions.leave')}
           description={t('chat.actions.leave.confirmation.text', { name: chatPartnerFullName })}
           testID="chat-room-leave-confirmation"
-          handleConfirmation={handleLeaveConfirmation}
+          handleConfirmation={() => handleLeaveConfirmation()}
           hasDismissBtn
           requireLogin
         />
diff --git a/packages/chat/src/components/ChatRoomHeaderTitle.tsx b/packages/chat/src/components/ChatRoomHeaderTitle.tsx
index 8f9777667f..804c71a342 100644
--- a/packages/chat/src/components/ChatRoomHeaderTitle.tsx
+++ b/packages/chat/src/components/ChatRoomHeaderTitle.tsx
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
 import { StyleSheet, View } from 'react-native'
 
 import { HoliLink } from '@holi/core/navigation/components/HoliLink'
-import HoliText from '@holi/ui/components/atoms/HoliText'
+import { Text } from 'holi-bricks/components/text'
 import { Avatar } from 'holi-bricks/components/avatar'
 import type { AvatarShape } from 'holi-bricks/components/avatar/Avatar'
 
@@ -12,18 +12,18 @@ interface Props {
   name: string
   imageSrc?: string
   imageBlurhash?: string
-  initials: string
+  initials?: string
   defaultColor?: string
   shape?: AvatarShape
 }
 
-const ChatRoomHeaderTitle = ({ href, name, ...avatarProps }: Props) => {
+const ChatRoomHeaderTitle = ({ href, name, initials, ...avatarProps }: Props) => {
   const { t } = useTranslation()
 
   const content = (
     <View style={styles.title}>
-      <Avatar label={name} {...avatarProps} size="xs" />
-      <HoliText bold>{name}</HoliText>
+      {initials && <Avatar label={name} initials={initials} {...avatarProps} size="xs" />}
+      <Text size="lg">{name}</Text>
     </View>
   )
 
diff --git a/packages/chat/src/components/ChatTextInput.tsx b/packages/chat/src/components/ChatTextInput.tsx
index f1e6bc05d4..eb9d5a88d9 100644
--- a/packages/chat/src/components/ChatTextInput.tsx
+++ b/packages/chat/src/components/ChatTextInput.tsx
@@ -28,10 +28,11 @@ interface Props {
   roomId: string
   isVisible: boolean
   userName?: string
+  isTaskChat?: boolean
   onMessageSent?: (content: string) => void
 }
 
-export const ChatTextInput = ({ roomId, isVisible, userName = '', onMessageSent }: Props) => {
+export const ChatTextInput = ({ roomId, isVisible, userName = '', isTaskChat = false, onMessageSent }: Props) => {
   const { t } = useTranslation()
   const { theme } = useTheme()
   const { displayError } = useErrorHandling()
@@ -58,12 +59,16 @@ export const ChatTextInput = ({ roomId, isVisible, userName = '', onMessageSent
     }
   }
 
+  const placeholderText = isTaskChat
+    ? t('chat.room.input.placeholder.taskchat')
+    : t('chat.room.input.placeholder', { fullName: userName })
+
   return isVisible ? (
     <View style={styles.wrapper}>
       <View style={styles.container}>
         <HoliTextInput
-          aria-label={t('chat.room.input.placeholder', { fullName: userName })}
-          placeholder={t('chat.room.input.placeholder', { fullName: userName })}
+          aria-label={placeholderText}
+          placeholder={placeholderText}
           value={inputValue}
           onChange={setInputValue}
           style={styles.textInputContainer}
diff --git a/packages/chat/src/components/RoomList/RoomItem.tsx b/packages/chat/src/components/RoomList/RoomItem.tsx
index c52cfcb41a..f6fe44ae05 100644
--- a/packages/chat/src/components/RoomList/RoomItem.tsx
+++ b/packages/chat/src/components/RoomList/RoomItem.tsx
@@ -32,7 +32,7 @@ const RoomItem = ({ room, loggedInUser }: RoomItemProps) => {
   const { theme } = useTheme()
   const styles = useMemo(() => getStyles(theme), [theme])
   const { id: roomId, name, hasInvitation, notificationCount, members } = room
-  const chatPartner = useAtomValue(useMemo(() => chatPartnerAtomCreator(roomId, loggedInUser), [loggedInUser, roomId]))
+  const chatPartners = useAtomValue(useMemo(() => chatPartnerAtomCreator(roomId, loggedInUser), [loggedInUser, roomId]))
 
   const hasUnseenMessages = notificationCount > 0
   const testID = `room-item-${name}`
@@ -67,6 +67,8 @@ const RoomItem = ({ room, loggedInUser }: RoomItemProps) => {
     return null
   }
 
+  const firstChatPartner = chatPartners?.[0]
+
   return (
     <HoliLink href={`/chat/rooms/${roomId}`} label={roomName} onPress={handleOnPress}>
       <HoliBox padding={[ROOM_ITEM_VERTICAL_PADDING, 0]}>
@@ -81,19 +83,19 @@ const RoomItem = ({ room, loggedInUser }: RoomItemProps) => {
                 shape="square"
                 size="md"
               />
-            ) : chatPartner ? (
-              isMatrixId(chatPartner.name) ? (
+            ) : firstChatPartner ? (
+              isMatrixId(firstChatPartner.name) ? (
                 <Avatar
                   label={roomName}
-                  imgSrc={chatPartner.avatar ?? ''}
-                  initials={getChatInitials(chatPartner.name)}
+                  imgSrc={firstChatPartner.avatar ?? ''}
+                  initials={getChatInitials(firstChatPartner.name)}
                   size="md"
                 />
               ) : (
                 <Avatar
                   label={roomName}
-                  imgSrc={chatPartner.avatar ?? ''}
-                  initials={getChatInitials(chatPartner.name)}
+                  imgSrc={firstChatPartner.avatar ?? ''}
+                  initials={getChatInitials(firstChatPartner.name)}
                   size="md"
                 />
               )
diff --git a/packages/chat/src/components/RoomMessageList/RoomMessageListStart.tsx b/packages/chat/src/components/RoomMessageList/RoomMessageListStart.tsx
index d86619ef73..f3aa604cad 100644
--- a/packages/chat/src/components/RoomMessageList/RoomMessageListStart.tsx
+++ b/packages/chat/src/components/RoomMessageList/RoomMessageListStart.tsx
@@ -1,53 +1,89 @@
-import { Text } from 'holi-bricks/components/text'
+import { Text, TextLink } from 'holi-bricks/components/text'
 import React from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 import { View } from 'react-native'
 
 import type { ChatRoomMember } from '@holi/chat/src/client/types'
 import type { User } from '@holi/core/domain/shared/types'
-import { HoliLink } from '@holi/core/navigation/components/HoliLink'
-import { HoliTextLink } from '@holi/core/navigation/components/HoliTextLink'
 import { HoliGap } from '@holi/ui/components/atoms/HoliGap'
-import { HoliHeading } from '@holi/ui/components/atoms/HoliHeading'
-import { Avatar } from 'holi-bricks/components/avatar'
+import AvatarPile from 'holi-bricks/components/avatar/AvatarPile'
+import useRouting from '@holi/core/navigation/hooks/useRouting'
 
 interface RoomMessageListStartProps {
-  chatPartnerHoliUser: User
-  member: ChatRoomMember | undefined
+  chatPartnerHoliUsers: User[]
+  members: ChatRoomMember[] | undefined
+  taskName?: string
+  taskId?: string
+  parentSpaceId?: string
+  spaceName?: string
 }
 
-const RoomMessageListStart = ({ chatPartnerHoliUser, member }: RoomMessageListStartProps) => {
+const RoomMessageListStart = ({
+  chatPartnerHoliUsers,
+  taskName,
+  taskId,
+  parentSpaceId,
+  spaceName,
+}: RoomMessageListStartProps) => {
   const { t } = useTranslation()
+  const { navigate } = useRouting()
+
+  const chatDescription = () => {
+    if (taskId) {
+      return (
+        <Text size="md" color="support">
+          <Trans
+            t={t}
+            i18nKey={'chat.room.start.group.description'}
+            values={{ taskName: taskName, spaceName: spaceName }}
+            components={{
+              fullName: (
+                <>
+                  {chatPartnerHoliUsers.map((user, index) => (
+                    <React.Fragment key={user.id}>
+                      {index > 0 && <Text size="md">, </Text>}
+                      <TextLink onPress={() => navigate(`/profile/${user.id}`)} size="md" color="informative">
+                        {user.fullName}
+                      </TextLink>
+                    </React.Fragment>
+                  ))}
+                </>
+              ),
+              taskName: (
+                <TextLink
+                  onPress={() => navigate(`/spaces/${parentSpaceId}/tasks/${taskId}`)}
+                  size="md"
+                  color="informative"
+                />
+              ),
+              spaceName: (
+                <TextLink onPress={() => navigate(`/spaces/${parentSpaceId}`)} size="md" color="informative" />
+              ),
+            }}
+          />
+        </Text>
+      )
+    }
+    return (
+      <Text size="md" color="support">
+        <Trans t={t} i18nKey={'chat.room.start.description'} values={{ fullName: chatPartnerHoliUsers[0].fullName }}>
+          <TextLink onPress={() => navigate(`/profile/${chatPartnerHoliUsers[0].id}`)} size="md" color="informative" />
+        </Trans>
+      </Text>
+    )
+  }
 
   return (
     <View>
-      <HoliLink
-        href={`/profile/${chatPartnerHoliUser.id}`}
-        label={t('user.visitProfile', { name: chatPartnerHoliUser.fullName })}
-      >
-        <Avatar
-          imgSrc={member?.avatar}
-          imgPlaceholder={chatPartnerHoliUser.avatarBlurhash}
-          label={chatPartnerHoliUser.fullName}
-          initials={chatPartnerHoliUser.avatarLabel}
-          size="lg"
-        />
-        <HoliGap size="xs" />
-
-        <HoliHeading level="2">{chatPartnerHoliUser.fullName}</HoliHeading>
-      </HoliLink>
+      <AvatarPile users={chatPartnerHoliUsers} size="lg" />
+      <HoliGap size="xs" />
+      <Text headingLevel="2" size="xxl">
+        {chatPartnerHoliUsers.map((user) => user.fullName).join(', ')}
+      </Text>
+
       <HoliGap size="s" />
 
-      <Text size="md">
-        <Trans t={t} i18nKey={'chat.room.start.description'} values={{ fullName: chatPartnerHoliUser.fullName }}>
-          <HoliTextLink
-            label={t('user.visitProfile', { name: chatPartnerHoliUser.fullName })}
-            href={`/profile/${chatPartnerHoliUser.id}`}
-            size="md"
-            color="informative"
-          />
-        </Trans>
-      </Text>
+      {chatDescription()}
     </View>
   )
 }
diff --git a/packages/chat/src/store/store.ts b/packages/chat/src/store/store.ts
index a805f1dd1d..186ba313ad 100644
--- a/packages/chat/src/store/store.ts
+++ b/packages/chat/src/store/store.ts
@@ -9,6 +9,7 @@ import {
   type ChatRoom,
   type ChatRoomMember,
   type ChatRoomMessage,
+  type ChatRoomType,
   type MatrixId,
   MembershipStatus,
   type NotificationCount,
@@ -16,6 +17,7 @@ import {
   type RoomUpdatesContent,
   RoomUpdatesType,
   type SpaceChatRoom,
+  type SpaceTaskRoom,
 } from '@holi/chat/src/client/types'
 import { createChatMessage } from '@holi/chat/src/client/utils'
 import {
@@ -221,11 +223,13 @@ _roomAtomsRecordAtomSubscriber.onMount = (setAtom) => {
    * Subscribe to incoming rooms
    */
   const unsubscribeNewRooms = client.subscribeToNewRooms(addRoomAtom)
+  const unsubscribeToRoomCreateEvent = client.subscribeToRoomCreateEvent(addRoomAtom)
   const unsubscribeRoomSharedData = client.subscribeToSharedData(addRoomAtom)
   const unsubscribeRoomNameEvents = client.subscribeToRoomNameEvent(addRoomAtom)
 
   return () => {
     unsubscribeNewRooms()
+    unsubscribeToRoomCreateEvent()
     unsubscribeRoomNameEvents()
     unsubscribeRoomSharedData()
   }
@@ -369,21 +373,29 @@ export const roomAtomsRecorAtomForTesting = atom(
  **/
 
 /**
- * @name createNewRoom (Client Helper)
- * @description Initiates the creation of a room in Matrix through the
- * ChatClient. The store updates once the room creation event is received from
- * the Matrix server.
+ * @name getOrCreateRoom (Client Helper)
+ * @description returns the room id of an existing one-on-one room, if it
+ * exists, or creates a new one.
  */
-export const createNewRoom = async (userIds: string[]): Promise<RoomId> => {
-  return getChatClient().createRoom(userIds, { isDirectMessage: true })
+export const getOrCreatePrivateRoom = async (userIds: string[], name?: string): Promise<RoomId> => {
+  return getChatClient().getOrCreateRoom(userIds, { isDirectMessage: true, name: name, metadata: { type: 'private' } })
 }
+
+export const getExistingRoomId = (userIds: string[], roomType?: ChatRoomType): string | undefined => {
+  return getChatClient().getExistingRoomId(userIds, roomType)
+}
+
 /**
- * @name getOrCreateRoom (Client Helper)
- * @description returns the room id of an existing one-on-one room, if it
+ * @name getOrCreateSpaceTaskRoom (Client Helper)
+ * @description returns the room id of an existing space-task room, if it
  * exists, or creates a new one.
  */
-export const getOrCreateRoom = async (userIds: string[]): Promise<RoomId> => {
-  return getChatClient().getOrCreateRoom(userIds, { isDirectMessage: true })
+export const getOrCreateSpaceTaskRoom = async (userIds: string[], metadata: SpaceTaskRoom): Promise<RoomId> => {
+  return getChatClient().getOrCreateRoom(userIds, {
+    isDirectMessage: true,
+    name: metadata.content.taskName,
+    metadata,
+  })
 }
 
 /**
@@ -702,13 +714,13 @@ const _membershipSubscriberAtom = atom(
     if (!roomAtom) return
 
     switch (membership) {
+      case 'invite':
       case 'join':
       case 'leave': {
         set(roomAtom, (room) => addOrUpdateMemberInChatRoom(room, member))
         break
       }
 
-      case 'invite':
       case 'ban':
       case 'knock':
       default:
@@ -735,15 +747,19 @@ _ignoredUsersAtomSubscriber.onMount = (setAtom) => {
  * @description An atom creator that gives you the partner member in a DM
  * conversion between two people.
  */
-export const chatPartnerAtomCreator = (roomId: string, holiUser?: HoliUser): Atom<ChatRoomMember | undefined> => {
+export const chatPartnerAtomCreator = (
+  roomId: string,
+  holiUserToExclude?: HoliUser
+): Atom<ChatRoomMember[] | undefined> => {
   return atom((get) => {
-    if (!holiUser) return
+    if (!holiUserToExclude) return
 
     const roomAtom = getAtomFromRecordId(get, _roomAtomsRecordAtom, roomId)
     if (!roomAtom) return
 
     const chatRoom = get(roomAtom)
-    return getChatPartner(holiUser, chatRoom)
+    const chatPartners = getChatPartners(holiUserToExclude, chatRoom)
+    return chatPartners.length ? chatPartners : undefined
   })
 }
 
@@ -798,11 +814,8 @@ export const hasOnlyIgnoredMembers = (members: ChatRoomMember[], ignoredUsers: M
   return ignoredRoomMembers.length === members.length - 1
 }
 
-export const getChatPartner = (holiUser: HoliUser, room: ChatRoom): ChatRoomMember | undefined => {
-  // TODO this will have to be changed once we support multiple users per room
-  return room.type === 'private'
-    ? room.members.find((member) => member.id !== toMatrixIdentity(holiUser.identity))
-    : undefined
+export const getChatPartners = (holiUserToExclude: HoliUser, room: ChatRoom): ChatRoomMember[] => {
+  return room.members.filter((member) => member.id !== toMatrixIdentity(holiUserToExclude.identity))
 }
 
 export const isRoomCreator = (user: HoliUser, room: ChatRoom): boolean | undefined => {
-- 
GitLab