@@ -472,7 +472,7 @@ beforeEach(() => {
experimentState.enabled = true;
viewportState.compact = false;
sidebarState.showing = true;
- collapsedRailState.isMainCollapsed = false;
+ panelFullScreenState.isMainCollapsed = false;
panelGroupLayoutState.layout = [100, 0];
commandHandlers.clear();
commandPresentationState.isModifierHeld = false;
@@ -535,8 +535,8 @@ describe("SplitThreadArea", () => {
});
});
- it("removes the collapsed-thread rail while maximized and keeps the restore control", async () => {
- collapsedRailState.isMainCollapsed = true;
+ it("temporarily replaces panel full screen with a clean thread full screen", async () => {
+ panelFullScreenState.isMainCollapsed = true;
renderSplitArea({
path: threadPath("thr-a"),
layout: twoPaneLayout("pane-1"),
@@ -545,15 +545,16 @@ describe("SplitThreadArea", () => {
expect(screen.queryByTestId("mock-collapsed-thread-rail")).toBeNull();
fireEvent.click(screen.getByTestId("maximize-thr-a"));
- expect(screen.queryByTestId("mock-collapsed-thread-rail")).toBeNull();
expect(screen.getByTestId("maximize-thr-a").textContent).toBe("restore");
await waitFor(() => {
- expect(panelGroupLayoutState.layout[0]).toBeGreaterThan(0);
- expect(panelGroupLayoutState.layout[1]).toBeLessThan(100);
+ expect(panelGroupLayoutState.layout).toEqual([100, 0]);
});
fireEvent.click(screen.getByTestId("maximize-thr-a"));
expect(screen.queryByTestId("mock-collapsed-thread-rail")).toBeNull();
+ await waitFor(() => {
+ expect(panelGroupLayoutState.layout).toEqual([0, 100]);
+ });
});
it("preserves a hidden pane's mounted scroll position through restore", async () => {
@@ -902,7 +903,7 @@ describe("SplitThreadArea", () => {
"[data-pane-header-focus-tab]",
);
expect(focusedTab).not.toBeNull();
- expect(focusedTab?.classList).toContain("bg-muted");
+ expect(focusedTab?.classList).toContain("bg-state-active");
expect(focusedTab?.classList).not.toContain("shadow-sm");
expect(screen.getByText("New thread").classList).toContain("font-normal");
expect(screen.getByText("New thread").classList).not.toContain(
diff --git a/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx b/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx
index ef8edb255..0e91edcf9 100644
--- a/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx
+++ b/apps/app/src/views/thread-detail/SplitWorkspaceSecondaryPanelHost.tsx
@@ -124,11 +124,15 @@ export function SplitWorkspaceSecondaryPanelHost({
// Both panels register with the group asynchronously on mount; until they
// have, setLayout throws and defaultSize already encodes this state.
if (group.getLayout().length !== 2) return;
+ if (isPaneMaximized) {
+ group.setLayout([MAIN_PANEL_OPEN_SIZE_PERCENT, 0]);
+ return;
+ }
if (!isOpen) {
group.setLayout([MAIN_PANEL_OPEN_SIZE_PERCENT, 0]);
return;
}
- if (model?.isMainCollapsed && !isPaneMaximized) {
+ if (model?.isMainCollapsed) {
group.setLayout([0, MAIN_PANEL_OPEN_SIZE_PERCENT]);
return;
}
@@ -179,8 +183,8 @@ export function SplitWorkspaceSecondaryPanelHost({
const toggleLabel = isOpen ? "Hide right panel" : "Show right panel";
const hostLayout = useMemo
(
- () => ({ isOpen }),
- [isOpen],
+ () => ({ isOpen, isSuppressed: isPaneMaximized }),
+ [isOpen, isPaneMaximized],
);
return (
@@ -196,6 +200,7 @@ export function SplitWorkspaceSecondaryPanelHost({
data-testid="split-workspace-panel-toggle"
className={cn(
"absolute right-2.5 top-2.5 z-40",
+ isPaneMaximized && "hidden",
// This overlay already owns positioning and stacking. Use only the
// raw app-region token: MACOS_WINDOW_NO_DRAG_CLASS adds `relative
// z-50`, which tailwind-merge would resolve against `absolute` and
@@ -238,11 +243,13 @@ export function SplitWorkspaceSecondaryPanelHost({
collapsible
collapsedSize={0}
defaultSize={
- model?.isMainCollapsed && !isPaneMaximized
- ? 0
- : isOpen
- ? MAIN_PANEL_OPEN_SIZE_PERCENT - panelWidthPercent
- : MAIN_PANEL_OPEN_SIZE_PERCENT
+ isPaneMaximized
+ ? MAIN_PANEL_OPEN_SIZE_PERCENT
+ : model?.isMainCollapsed
+ ? 0
+ : isOpen
+ ? MAIN_PANEL_OPEN_SIZE_PERCENT - panelWidthPercent
+ : MAIN_PANEL_OPEN_SIZE_PERCENT
}
minSize={MAIN_PANEL_MIN_SIZE_PERCENT}
order={1}
@@ -252,10 +259,7 @@ export function SplitWorkspaceSecondaryPanelHost({
)}
>
{/* Panel renders a plain block; the split tree sizes itself with
- flex-1, so restore a full-height flex context for it. `relative`
- makes this the containing block for a maximized pane's
- `absolute inset-0`, so fullscreening a pane fills the main panel
- without covering the open secondary panel to its right. */}
+ flex-1, so restore a full-height flex context for it. */}
{children}
diff --git a/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx b/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx
index 61b4f0d50..6f744b327 100644
--- a/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx
+++ b/apps/app/src/views/thread-detail/ThreadDetailHeader.test.tsx
@@ -142,9 +142,7 @@ describe("ThreadDetailHeader", () => {
expect(screen.getByText("Responsive menu actions")).not.toBeNull();
const closePane = screen.getByRole("button", { name: "Close pane" });
expect(closePane.classList).toContain("header-pane-action-button");
- const closeIcon = closePane.querySelector(
- '[data-icon="CloseThreadPane"]',
- );
+ const closeIcon = closePane.querySelector('[data-icon="CloseThreadPane"]');
expect(closeIcon).not.toBeNull();
expect(closeIcon?.querySelectorAll("path")).toHaveLength(1);
expect(closeIcon?.querySelector("path")?.getAttribute("d")).toContain(
@@ -250,7 +248,7 @@ describe("ThreadDetailHeader", () => {
"[data-pane-header-focus-tab]",
);
expect(focusedTab).not.toBeNull();
- expect(focusedTab?.classList).toContain("bg-muted");
+ expect(focusedTab?.classList).toContain("bg-state-active");
expect(focusedTab?.classList).not.toContain("shadow-sm");
expect(container.querySelector("[data-app-page-header-dim]")).toBeNull();
const activeTitle = screen.getByText("Focused thread");