@@ -56,18 +56,35 @@ export default function ChannelConversationScreen() {
5656 const [ editText , setEditText ] = useState ( '' ) ;
5757 const [ imageUri , setImageUri ] = useState < string | null > ( null ) ;
5858 const [ presenceIds , setPresenceIds ] = useState < Set < string > > ( new Set ( ) ) ;
59+ const [ resolveAttempted , setResolveAttempted ] = useState ( false ) ;
5960 const unsubscribeRef = useRef < ( ( ) => void ) | null > ( null ) ;
6061
6162 const isDm = channel ?. ChannelType === ChatChannelType . DirectMessage ;
6263 const showSender = ! isDm ;
64+ const isChatbot = channel ?. ChannelType === ChatChannelType . Chatbot ;
65+ // Deep links (push notifications, cold starts) can arrive before the channel
66+ // list loads; the channel type is unknown until then. Treat a completed fetch
67+ // with no match as resolved so unknown channels keep the generic screen.
68+ const isResolved = ! ! channel || resolveAttempted ;
6369
6470 // Newest-first for the inverted list.
6571 const inverted = useMemo ( ( ) => ( messages ? messages . slice ( ) . reverse ( ) : [ ] ) , [ messages ] ) ;
6672
67- // Mount: activate channel, join hub, load history and members.
73+ // Resolve the channel identity for deep links before mounting the generic view.
74+ useEffect ( ( ) => {
75+ if ( channel || resolveAttempted || ! isChatEnabled ) return ;
76+ void useChatStore
77+ . getState ( )
78+ . fetchChannels ( )
79+ . finally ( ( ) => setResolveAttempted ( true ) ) ;
80+ } , [ channel , resolveAttempted , isChatEnabled ] ) ;
81+
82+ // Mount: activate channel, join hub, load history and members. Assistant
83+ // conversations are handled by the dedicated chatbot screen — never join or
84+ // load them here, and wait for unresolved deep links to identify first.
6885 useFocusEffect (
6986 useCallback ( ( ) => {
70- if ( ! channelId || ! isChatEnabled ) return ;
87+ if ( ! channelId || ! isChatEnabled || ! isResolved || isChatbot ) return ;
7188 const store = useChatStore . getState ( ) ;
7289 store . setActiveChannel ( channelId ) ;
7390 void store . joinChannel ( channelId ) ;
@@ -76,7 +93,7 @@ export default function ChannelConversationScreen() {
7693 return ( ) => {
7794 useChatStore . getState ( ) . setActiveChannel ( null ) ;
7895 } ;
79- } , [ channelId , isChatEnabled ] )
96+ } , [ channelId , isChatEnabled , isResolved , isChatbot ] )
8097 ) ;
8198
8299 // Fetch presence for the channel members (for the header online dot).
@@ -96,10 +113,10 @@ export default function ChannelConversationScreen() {
96113
97114 // Mark read whenever the newest message changes while viewing.
98115 useEffect ( ( ) => {
99- if ( channelId && inverted . length > 0 ) {
116+ if ( channelId && isResolved && ! isChatbot && inverted . length > 0 ) {
100117 void useChatStore . getState ( ) . markChannelRead ( channelId ) ;
101118 }
102- } , [ channelId , inverted . length ] ) ;
119+ } , [ channelId , inverted . length , isResolved , isChatbot ] ) ;
103120
104121 const otherOnline = useMemo ( ( ) => {
105122 if ( ! isDm ) return false ;
@@ -254,9 +271,20 @@ export default function ChannelConversationScreen() {
254271 return < Redirect href = { '/home' as Href } /> ;
255272 }
256273
274+ // Deep link to a channel that isn't loaded yet: wait for the channel list so
275+ // assistant conversations never mount the full-featured view.
276+ if ( ! isResolved ) {
277+ return (
278+ < Box className = "size-full flex-1 items-center justify-center bg-background-0" >
279+ < Stack . Screen options = { { title, headerShown : true , headerBackTitle : '' } } />
280+ < Spinner />
281+ </ Box >
282+ ) ;
283+ }
284+
257285 // Assistant conversations always use the dedicated restricted screen (text only,
258286 // no reactions/threads/deletes) — catch deep links and stale routes here.
259- if ( channel ?. ChannelType === ChatChannelType . Chatbot ) {
287+ if ( isChatbot ) {
260288 return < Redirect href = { '/chatbot' as Href } /> ;
261289 }
262290
0 commit comments