From 323d55a43b2dfc26145fa89a4af44cc602542cf5 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 23 Aug 2026 10:03:46 -0700 Subject: [PATCH] FN-9196: Move archived chat toggle into filter row Restyle the chat archive control as a compact, responsive filter-line toggle. - Place the Archived toggle beside the tag filter with stable pressed-state semantics. - Add token-based responsive styling for desktop, narrow, and mobile sidebars. - Cover toggle behavior, selected-tag coexistence, and filter-row layout. - Add a patch changeset for the published Fusion package. Files changed: .changeset/chat-archived-filter-row.md | 7 ++ packages/dashboard/app/components/ChatView.css | 17 +++- packages/dashboard/app/components/ChatView.tsx | 44 +++++++--- .../ChatView.archived-toggle-row.test.tsx | 99 ++++++++++++++++++++++ 4 files changed, 151 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-9196 Fusion-Task-Lineage: 2bd3d000-84be-4798-9b73-de1feac40536 Co-authored-by: Fusion (runfusion.ai) --- .changeset/chat-archived-filter-row.md | 7 ++ .../dashboard/app/components/ChatView.css | 17 +++- .../dashboard/app/components/ChatView.tsx | 44 ++++++--- .../ChatView.archived-toggle-row.test.tsx | 99 +++++++++++++++++++ 4 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 .changeset/chat-archived-filter-row.md create mode 100644 packages/dashboard/app/components/__tests__/ChatView.archived-toggle-row.test.tsx diff --git a/.changeset/chat-archived-filter-row.md b/.changeset/chat-archived-filter-row.md new file mode 100644 index 0000000000..2e21429d17 --- /dev/null +++ b/.changeset/chat-archived-filter-row.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Chat sidebar shows a compact Archived toggle on the tag filter line. +category: fix +dev: ChatView sidebar filter row (.chat-sidebar-filter-row); .chat-archived-toggle restyled, testid unchanged. diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index 14a614a9d0..b90e9efcdd 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -706,6 +706,10 @@ A narrow floating host keeps the one-pane list/detail flow. FN-9193 adds a docke min-height: calc(var(--space-2xl) + var(--space-xs)); } +.chat-view--narrow .chat-sidebar-filter-row { + flex-wrap: wrap; +} + .chat-view--narrow .chat-sidebar-list { flex: 1; min-height: 0; @@ -2498,9 +2502,17 @@ Shared ChatView hosts expose Direct-only tag filters and compact chips without i FNXC:ChatTags 2026-08-23-15:51: The tag filter select must use the sibling sidebar search input's comfortable token-based inner padding so "All tags" is not flush against its border across desktop, narrow, and mobile hosts. + +FNXC:ChatArchived 2026-08-23-16:27: +The compact Archived toggle shares the narrowed tag-filter line, with tokenized row padding separating the search input and session list. Narrow and mobile sidebars may wrap the controls rather than clipping or horizontally overflowing them. */ -.chat-tag-filter { display: flex; align-items: center; gap: var(--space-xs); color: var(--text-muted); } -.chat-tag-filter select { flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg); color: var(--text); padding: var(--space-sm) var(--space-md); } +.chat-sidebar-filter-row { display: flex; align-items: center; gap: var(--space-xs); padding: var(--space-xs) 0; min-width: 0; } +.chat-tag-filter { display: flex; flex: 1 1 auto; align-items: center; gap: var(--space-xs); min-width: 0; overflow: hidden; color: var(--text-muted); } +.chat-tag-filter > svg, +.chat-tag-filter > .btn-icon { flex: 0 0 auto; } +.chat-tag-filter select { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg); color: var(--text); padding: var(--space-sm) var(--space-md); } +.chat-archived-toggle { flex: 0 0 auto; padding-inline: var(--space-sm); white-space: nowrap; } +.chat-archived-toggle--active { background: color-mix(in srgb, var(--accent) 16%, var(--surface-2)); color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); } .chat-session-tags { display: flex; flex-wrap: wrap; gap: var(--space-xs); overflow: hidden; } .chat-session-tag { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 var(--space-xs); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text-muted); font-size: var(--font-size-xs); } /* FNXC:ChatTags 2026-08-05-12:15: direct-conversation tag controls keep assignment, rename, and deletion in the existing context menu so shared Chat hosts do not grow another sidebar action row. */ @@ -2511,6 +2523,7 @@ The tag filter select must use the sibling sidebar search input's comfortable to .chat-tag-create-row { display: flex; gap: var(--space-xs); } .chat-tag-create-row .input { min-width: 0; } @media (max-width: 768px) { + .chat-sidebar-filter-row { flex-wrap: wrap; } .chat-session-tags { max-height: calc(var(--space-lg) * 2); } /* FNXC:ChatTags 2026-07-24-23:05: diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index e17a8acd2f..5a05500efc 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -3508,23 +3508,39 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout data-testid="chat-search-input" /> - + : null} - + {t("chat.archived", "Archived")} + + {/* Session list section */} -
{sessionsLoading ? (
{t("chat.loadingConversations", "Loading...")}
diff --git a/packages/dashboard/app/components/__tests__/ChatView.archived-toggle-row.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.archived-toggle-row.test.tsx new file mode 100644 index 0000000000..19628bcfb9 --- /dev/null +++ b/packages/dashboard/app/components/__tests__/ChatView.archived-toggle-row.test.tsx @@ -0,0 +1,99 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { screen, within } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; +import { ChatView } from "../ChatView"; +import { installChatViewEnv, renderWithAct, setupMockChat } from "./ChatView.test-harness"; + +vi.mock("../../hooks/useChat"); +vi.mock("../../hooks/useChatRooms"); +vi.mock("../../hooks/useNavigationHistory", async (importOriginal) => ({ + ...(await importOriginal()), + useNavigationHistoryContext: () => ({ pushNav: vi.fn(), replaceCurrent: vi.fn() }), +})); +vi.mock("../../api", () => ({ + fetchSettings: vi.fn().mockResolvedValue({}), + fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [], favoriteModels: [], defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5" }), + fetchAgents: vi.fn().mockResolvedValue([]), + fetchDiscoveredSkills: vi.fn().mockResolvedValue([]), + fetchTasks: vi.fn().mockResolvedValue([]), + searchFiles: vi.fn().mockResolvedValue({ files: [] }), +})); + +installChatViewEnv(); + +const chatViewCss = readFileSync(resolve(__dirname, "../ChatView.css"), "utf8"); +const archivedSession = { + id: "session-archived", + agentId: "agent-002", + status: "archived" as const, + title: "Archived conversation", + createdAt: "2026-08-23T00:00:00.000Z", + updatedAt: "2026-08-23T00:00:00.000Z", +}; + +function getFilterRow() { + const toggle = screen.getByTestId("chat-archived-toggle"); + const row = toggle.closest(".chat-sidebar-filter-row"); + expect(row).toContainElement(screen.getByTestId("chat-tag-filter")); + expect(row?.parentElement).toHaveClass("chat-sidebar-search-container"); + return row!; +} + +describe("ChatView archived toggle filter row", () => { + it("keeps the Archived label and archive behavior in the shared filter row", async () => { + const refreshArchivedSessions = vi.fn().mockResolvedValue(undefined); + setupMockChat({ archivedSessions: [archivedSession], refreshArchivedSessions }); + const user = userEvent.setup(); + + await renderWithAct(); + + const toggle = screen.getByTestId("chat-archived-toggle"); + expect(toggle).toHaveTextContent(/^Archived$/); + expect(toggle).toHaveAttribute("aria-pressed", "false"); + expect(screen.queryByText("Archived conversations")).not.toBeInTheDocument(); + expect(screen.queryByText("Active conversations")).not.toBeInTheDocument(); + getFilterRow(); + expect(document.querySelectorAll("[data-testid=\"chat-archived-toggle\"]")).toHaveLength(1); + expect(document.querySelector(".chat-sidebar-search-container")?.nextElementSibling).toHaveClass("chat-session-list"); + + await user.click(toggle); + expect(toggle).toHaveTextContent(/^Archived$/); + expect(toggle).toHaveAttribute("aria-pressed", "true"); + expect(refreshArchivedSessions).toHaveBeenCalledOnce(); + expect(screen.getByTestId("chat-archived-session-session-archived")).toBeInTheDocument(); + + await user.click(toggle); + expect(toggle).toHaveTextContent(/^Archived$/); + expect(toggle).toHaveAttribute("aria-pressed", "false"); + expect(refreshArchivedSessions).toHaveBeenCalledOnce(); + }); + + it("keeps populated selected-tag controls beside Archived", async () => { + const tag = { + id: "tag-long", + projectId: "proj-123", + name: "A very long tag name that must not displace the archive toggle", + createdAt: "2026-08-23T00:00:00.000Z", + updatedAt: "2026-08-23T00:00:00.000Z", + }; + setupMockChat({ tags: [tag], selectedTagId: tag.id, setSelectedTagId: vi.fn() }); + + await renderWithAct(); + + const row = getFilterRow(); + expect(within(row).getByLabelText("Clear tag filter")).toBeInTheDocument(); + expect(within(row).getByTestId("chat-archived-toggle")).toHaveTextContent(/^Archived$/); + }); + + it("declares a token-padded flex filter row without a full-row tag filter", () => { + const rowRule = chatViewCss.match(/(?:^|\n)\.chat-sidebar-filter-row\s*\{([^{}]*)\}/)?.[1]; + const tagFilterRule = chatViewCss.match(/(?:^|\n)\.chat-tag-filter\s*\{([^{}]*)\}/)?.[1]; + + expect(rowRule).toMatch(/display:\s*flex/); + expect(rowRule).toMatch(/padding:\s*var\(--space-/); + expect(tagFilterRule).toContain("flex: 1 1 auto"); + expect(tagFilterRule).not.toMatch(/flex:\s*1\s*;/); + }); +});