From f270f30799d45fd860b49557a3f2d5563802fd62 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 12 Apr 2026 06:06:28 -0500 Subject: [PATCH] Add mobile collapse for inbox workspace groups --- ui/src/pages/Inbox.test.tsx | 51 +++++++++++++++++++++++++++- ui/src/pages/Inbox.tsx | 66 +++++++++++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 8 deletions(-) diff --git a/ui/src/pages/Inbox.test.tsx b/ui/src/pages/Inbox.test.tsx index 4a3c61994e6..cc1300d489d 100644 --- a/ui/src/pages/Inbox.test.tsx +++ b/ui/src/pages/Inbox.test.tsx @@ -5,7 +5,7 @@ import type { ComponentProps } from "react"; import { createRoot } from "react-dom/client"; import type { Issue } from "@paperclipai/shared"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { FailedRunInboxRow, InboxIssueMetaLeading, InboxIssueTrailingColumns } from "./Inbox"; +import { FailedRunInboxRow, InboxGroupHeader, InboxIssueMetaLeading, InboxIssueTrailingColumns } from "./Inbox"; vi.mock("@/lib/router", () => ({ Link: ({ children, className, ...props }: ComponentProps<"a">) => ( @@ -245,3 +245,52 @@ describe("InboxIssueTrailingColumns", () => { }); }); }); + +describe("InboxGroupHeader", () => { + let container: HTMLDivElement; + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + }); + + it("shows a left caret and expanded state for collapsible mobile headers", () => { + const root = createRoot(container); + + act(() => { + root.render(); + }); + + const button = container.querySelector("button"); + expect(button).not.toBeNull(); + expect(button?.getAttribute("aria-expanded")).toBe("true"); + expect(button?.textContent).toContain("Primary workspace (default)"); + const caret = container.querySelector("svg"); + expect(caret?.className.baseVal).toContain("rotate-90"); + + act(() => { + root.unmount(); + }); + }); + + it("keeps the caret collapsed when the mobile group is hidden", () => { + const root = createRoot(container); + + act(() => { + root.render(); + }); + + const button = container.querySelector("button"); + expect(button?.getAttribute("aria-expanded")).toBe("false"); + const caret = container.querySelector("svg"); + expect(caret?.className.baseVal).not.toContain("rotate-90"); + + act(() => { + root.unmount(); + }); + }); +}); diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index c438bea1f05..96d53d30312 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -149,6 +149,47 @@ type InboxGroupedSection = { isArchivedSearch: boolean; }; +export function InboxGroupHeader({ + label, + collapsible = false, + collapsed = false, + onToggle, +}: { + label: string; + collapsible?: boolean; + collapsed?: boolean; + onToggle?: () => void; +}) { + if (collapsible) { + return ( + + ); + } + + return ( +
+
+ + {label} + +
+
+ ); +} + function buildGroupedInboxSections( items: InboxWorkItem[], groupBy: InboxWorkItemGroupBy, @@ -1101,6 +1142,15 @@ export function Inbox() { }); }, []); const [collapsedInboxParents, setCollapsedInboxParents] = useState>(new Set()); + const [collapsedGroupKeys, setCollapsedGroupKeys] = useState>(new Set()); + const toggleGroupCollapse = useCallback((groupKey: string) => { + setCollapsedGroupKeys((prev) => { + const next = new Set(prev); + if (next.has(groupKey)) next.delete(groupKey); + else next.add(groupKey); + return next; + }); + }, []); const groupedSections = useMemo(() => [ ...buildGroupedInboxSections(effectiveWorkItems, groupBy, nestingEnabled, inboxWorkspaceGrouping), ...buildGroupedInboxSections( @@ -2063,6 +2113,8 @@ export function Inbox() { let previousTimestamp = Number.POSITIVE_INFINITY; return groupedSections.flatMap((group, groupIndex) => { const elements: ReactNode[] = []; + const mobileWorkspaceGrouping = isMobile && groupBy === "workspace"; + const isGroupCollapsed = mobileWorkspaceGrouping && collapsedGroupKeys.has(group.key); if (group.isArchivedSearch && (groupIndex === 0 || !groupedSections[groupIndex - 1]?.isArchivedSearch)) { elements.push(
0 && "border-t border-border", )} > -
-
- - {group.label} - -
-
+ toggleGroupCollapse(group.key) : undefined} + />
, ); } + if (isGroupCollapsed) return elements; for (let index = 0; index < group.displayItems.length; index += 1) { const item = group.displayItems[index]!;