fix(site): stay on archived chat instead of redirecting (#22505)

When archiving a chat, the frontend no longer navigates away to a
different chat. Instead it stays on the current chat and shows an
archived state.

## Changes

**AgentsPage.tsx** — Removed the redirect logic from
`requestArchiveAgent`. After a successful archive, invalidates the
individual chat query so the detail view picks up the `archived` flag
immediately.

**AgentDetail.tsx** — Detects `chatRecord.archived` and:
- Disables the chat input
- Shows a banner: "This agent has been archived and is read-only."
- Passes `isArchived` to the top bar
- Guards `handleArchiveAgentAction` against double-archiving

**AgentDetail/TopBar.tsx** — When `isArchived`:
- Shows an "Archived" badge next to the chat title
- Hides the "Archive Agent" dropdown menu item

**AgentDetail/TopBar.stories.tsx** — Added an `Archived` story variant.
This commit is contained in:
Kyle Carberry
2026-03-02 19:43:45 +00:00
committed by GitHub
parent bdc1a0e798
commit 2f684002b8
4 changed files with 37 additions and 21 deletions
+11 -2
View File
@@ -15,6 +15,7 @@ import { workspaceById } from "api/queries/workspaces";
import type * as TypesGen from "api/typesGenerated";
import type { ModelSelectorOption } from "components/ai-elements";
import { Skeleton } from "components/Skeleton/Skeleton";
import { ArchiveIcon } from "lucide-react";
import { getVSCodeHref, SESSION_TOKEN_PLACEHOLDER } from "modules/apps/apps";
import {
type FC,
@@ -514,6 +515,7 @@ const AgentDetail: FC = () => {
const workspaceAgent = getWorkspaceAgent(workspace, undefined);
const chatData = chatQuery.data;
const chatRecord = chatData?.chat;
const isArchived = chatRecord?.archived ?? false;
const chatMessages = chatData?.messages;
const chatQueuedMessages = chatData?.queued_messages;
const chatLastModelConfigID = chatRecord?.last_model_config_id;
@@ -637,7 +639,7 @@ const AgentDetail: FC = () => {
sendMutation.isPending ||
editMutation.isPending ||
interruptMutation.isPending;
const isInputDisabled = !hasModelOptions;
const isInputDisabled = !hasModelOptions || isArchived;
const handleSend = async (message: string, editedMessageID?: number) => {
if (
@@ -853,7 +855,7 @@ const AgentDetail: FC = () => {
};
const handleArchiveAgentAction = () => {
if (!agentId) {
if (!agentId || isArchived) {
return;
}
requestArchiveAgent(agentId);
@@ -983,9 +985,16 @@ const AgentDetail: FC = () => {
onViewWorkspace: handleViewWorkspace,
}}
onArchiveAgent={handleArchiveAgentAction}
isArchived={isArchived}
isSidebarCollapsed={isSidebarCollapsed}
onToggleSidebarCollapsed={onToggleSidebarCollapsed}
/>
{isArchived && (
<div className="flex shrink-0 items-center gap-2 border-b border-border-default bg-surface-secondary px-4 py-2 text-sm text-content-secondary">
<ArchiveIcon className="h-4 w-4 shrink-0" />
This agent has been archived and is read-only.
</div>
)}
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 z-10 h-6 bg-surface-primary"
@@ -91,6 +91,12 @@ export const SidebarCollapsed: Story = {
},
};
export const Archived: Story = {
args: {
isArchived: true,
},
};
export const NoTitle: Story = {
args: {
chatTitle: undefined,
@@ -81,6 +81,7 @@ type AgentDetailTopBarProps = {
diff: DiffPanelState;
workspace: WorkspaceActions;
onArchiveAgent: () => void;
isArchived?: boolean;
isSidebarCollapsed: boolean;
onToggleSidebarCollapsed: () => void;
};
@@ -92,6 +93,7 @@ export const AgentDetailTopBar: FC<AgentDetailTopBarProps> = ({
diff,
workspace,
onArchiveAgent,
isArchived,
isSidebarCollapsed,
onToggleSidebarCollapsed,
}) => {
@@ -143,6 +145,11 @@ export const AgentDetailTopBar: FC<AgentDetailTopBarProps> = ({
<span className="truncate text-sm text-content-primary">
{chatTitle}
</span>
{isArchived && (
<span className="shrink-0 rounded bg-surface-tertiary px-1.5 py-0.5 text-xs text-content-secondary">
Archived
</span>
)}
</div>
)}
</div>
@@ -192,13 +199,15 @@ export const AgentDetailTopBar: FC<AgentDetailTopBarProps> = ({
<MonitorIcon className="h-3.5 w-3.5" />
View Workspace
</DropdownMenuItem>
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onSelect={onArchiveAgent}
>
<ArchiveIcon className="h-3.5 w-3.5" />
Archive Agent
</DropdownMenuItem>
{!isArchived && (
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onSelect={onArchiveAgent}
>
<ArchiveIcon className="h-3.5 w-3.5" />
Archive Agent
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<WebPushButton />
+4 -12
View File
@@ -221,29 +221,21 @@ const AgentsPage: FC = () => {
}
setArchivingChatId(chatId);
const nextChatId = (
queryClient.getQueryData(chats().queryKey) as
| TypesGen.Chat[]
| undefined
)?.find((chat) => chat.id !== chatId)?.id;
try {
await archiveMutation.mutateAsync(chatId);
clearChatErrorReason(chatId);
// Invalidate the individual chat query so the detail view
// picks up the archived flag without a redirect.
await queryClient.invalidateQueries({ queryKey: chatKey(chatId) });
toast.success("Agent archived.");
if (chatId === agentId) {
navigate(nextChatId ? `/agents/${nextChatId}` : "/agents", {
replace: true,
});
}
} catch (error) {
toast.error(getErrorMessage(error, "Failed to archive agent."));
} finally {
setArchivingChatId(null);
}
},
[archiveMutation, queryClient, agentId, navigate, clearChatErrorReason],
[archiveMutation, queryClient, clearChatErrorReason],
);
const handleToggleSidebarCollapsed = useCallback(
() => setIsSidebarCollapsed((prev) => !prev),