Guard task surfaces and route targets

This commit is contained in:
klopez4212
2026-06-30 13:55:18 +01:00
parent 9deef79d0e
commit 86e6db1e05
2 changed files with 122 additions and 62 deletions
@@ -78,6 +78,7 @@ import {
mergeAgentNamesIntoProfiles,
useChannelActivityTyping,
} from "./useChannelActivityTyping";
import { useAgentConversationRouteTarget } from "./useAgentConversationRouteTarget";
import { useChannelAgentSessions } from "./useChannelAgentSessions";
import { useChannelPanelHistoryState } from "./useChannelPanelHistoryState";
import { useChannelProfilePanel } from "./useChannelProfilePanel";
@@ -166,12 +167,21 @@ export function ChannelScreen({
const mainInsetRef = useMainInsetRef();
const currentPubkey = currentIdentity?.pubkey;
const activeChannelId = activeChannel?.id ?? null;
const canShowTasksSurface = activeChannel?.channelType === "stream";
const effectiveSurfaceTab = canShowTasksSurface
? activeSurfaceTab
: "messages";
useResetChannelSurfaceTabOnRouteOpen({
activeChannelId,
openThreadHeadId,
setActiveSurfaceTab,
targetMessageId,
});
React.useEffect(() => {
if (!canShowTasksSurface && activeSurfaceTab === "tasks") {
setActiveSurfaceTab("messages");
}
}, [activeSurfaceTab, canShowTasksSurface]);
const effectiveOpenThreadHeadId =
optimisticOpenThreadHeadId === undefined
? openThreadHeadId
@@ -325,6 +335,11 @@ export function ChannelScreen({
const messageProfilesQuery = useUsersBatchQuery(messageProfilePubkeys, {
enabled: messageProfilePubkeys.length > 0,
});
const messageProfilesReady =
messageProfilePubkeys.length === 0 ||
(!messageProfilesQuery.isPending &&
!messageProfilesQuery.isFetching &&
!messageProfilesQuery.isPlaceholderData);
const channelMembersQuery = useChannelMembersQuery(activeChannel?.id ?? null);
const channelMembers = channelMembersQuery.data;
const managedAgentsQuery = useManagedAgentsQuery();
@@ -744,71 +759,15 @@ export function ChannelScreen({
setProfilePanelPubkey,
],
);
const handledAgentConversationRouteTargetRef = React.useRef<string | null>(
null,
);
React.useEffect(() => {
if (!targetAgentConversationReplyId) {
handledAgentConversationRouteTargetRef.current = null;
return;
}
const targetKey = `${activeChannelId ?? "none"}:${targetAgentConversationReplyId}`;
if (handledAgentConversationRouteTargetRef.current === targetKey) {
return;
}
if (!activeChannel || activeChannel.channelType === "forum") {
return;
}
const agentReply =
timelineMessages.find(
(message) => message.id === targetAgentConversationReplyId,
) ?? null;
const agentReplyPubkey = agentReply?.pubkey;
if (!agentReply || !agentReplyPubkey) {
return;
}
const rootId = agentReply.rootId ?? agentReply.parentId ?? agentReply.id;
const contextMessages = timelineMessages.filter(
(candidate) =>
candidate.id === rootId ||
candidate.id === agentReply.id ||
candidate.rootId === rootId ||
candidate.parentId === rootId,
);
const parentMessage = agentReply.parentId
? (timelineMessages.find(
(candidate) => candidate.id === agentReply.parentId,
) ?? null)
: null;
const threadRootMessage =
timelineMessages.find((candidate) => candidate.id === rootId) ?? null;
handledAgentConversationRouteTargetRef.current = targetKey;
void goChannel(activeChannel.id, { replace: true }).then(() => {
openAgentConversation(
{
agentName: agentReply.author,
agentPubkey: agentReplyPubkey,
agentReply,
channel: activeChannel,
contextMessages,
parentMessage,
threadRootMessage,
},
{ publishMarker: false },
);
});
}, [
useAgentConversationRouteTarget({
activeChannel,
activeChannelId,
goChannel,
messageProfilesReady,
openAgentConversation,
targetAgentConversationReplyId,
timelineMessages,
]);
});
const mainTimelineTargetMessageId = useChannelRouteTarget({
activeChannel,
activeChannelId,
@@ -938,7 +897,7 @@ export function ChannelScreen({
activeChannel={activeChannel}
activeChannelEphemeralDisplay={activeChannelEphemeralDisplay}
activeChannelTitle={activeChannelTitle}
activeSurfaceTab={activeSurfaceTab}
activeSurfaceTab={effectiveSurfaceTab}
actionsVariant={shouldCompactHeaderActions ? "compact" : "inline"}
activeDmAvatarUrl={activeDmAvatarUrl}
activeDmHeaderParticipants={activeDmHeaderParticipants}
@@ -964,9 +923,9 @@ export function ChannelScreen({
activeDmAvatarUrl,
activeDmHeaderParticipants,
activeDmPresenceStatus,
activeSurfaceTab,
channelHeaderChromeRef,
currentPubkey,
effectiveSurfaceTab,
handleSurfaceTabChange,
isAddBotOpen,
joinChannelMutation.isPending,
@@ -1098,7 +1057,7 @@ export function ChannelScreen({
profilePanelView={profilePanelView}
personaLookup={personaLookup}
profiles={messageProfiles}
surfaceTab={activeSurfaceTab}
surfaceTab={effectiveSurfaceTab}
onSurfaceTabChange={handleSurfaceTabChange}
firstUnreadMessageId={firstUnreadMessageId}
unreadCount={unreadCount}
@@ -0,0 +1,101 @@
import * as React from "react";
import type { useAppNavigation } from "@/app/navigation/useAppNavigation";
import type { OpenAgentConversationInput } from "@/features/agents/agentConversations";
import type { TimelineMessage } from "@/features/messages/types";
import type { Channel } from "@/shared/api/types";
type GoChannel = ReturnType<typeof useAppNavigation>["goChannel"];
type OpenAgentConversation = (
input: OpenAgentConversationInput,
options?: { publishMarker?: boolean },
) => void;
type UseAgentConversationRouteTargetOptions = {
activeChannel: Channel | null;
activeChannelId: string | null;
goChannel: GoChannel;
messageProfilesReady: boolean;
openAgentConversation: OpenAgentConversation;
targetAgentConversationReplyId: string | null;
timelineMessages: readonly TimelineMessage[];
};
export function useAgentConversationRouteTarget({
activeChannel,
activeChannelId,
goChannel,
messageProfilesReady,
openAgentConversation,
targetAgentConversationReplyId,
timelineMessages,
}: UseAgentConversationRouteTargetOptions) {
const handledRouteTargetRef = React.useRef<string | null>(null);
React.useEffect(() => {
if (!targetAgentConversationReplyId) {
handledRouteTargetRef.current = null;
return;
}
const targetKey = `${activeChannelId ?? "none"}:${targetAgentConversationReplyId}`;
if (handledRouteTargetRef.current === targetKey) {
return;
}
if (!activeChannel || activeChannel.channelType === "forum") {
return;
}
if (!messageProfilesReady) {
return;
}
const agentReply =
timelineMessages.find(
(message) => message.id === targetAgentConversationReplyId,
) ?? null;
const agentReplyPubkey = agentReply?.pubkey;
if (!agentReply || !agentReplyPubkey) {
return;
}
const rootId = agentReply.rootId ?? agentReply.parentId ?? agentReply.id;
const contextMessages = timelineMessages.filter(
(candidate) =>
candidate.id === rootId ||
candidate.id === agentReply.id ||
candidate.rootId === rootId ||
candidate.parentId === rootId,
);
const parentMessage = agentReply.parentId
? (timelineMessages.find(
(candidate) => candidate.id === agentReply.parentId,
) ?? null)
: null;
const threadRootMessage =
timelineMessages.find((candidate) => candidate.id === rootId) ?? null;
handledRouteTargetRef.current = targetKey;
void goChannel(activeChannel.id, { replace: true }).then(() => {
openAgentConversation(
{
agentName: agentReply.author,
agentPubkey: agentReplyPubkey,
agentReply,
channel: activeChannel,
contextMessages,
parentMessage,
threadRootMessage,
},
{ publishMarker: false },
);
});
}, [
activeChannel,
activeChannelId,
goChannel,
messageProfilesReady,
openAgentConversation,
targetAgentConversationReplyId,
timelineMessages,
]);
}