Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 0568bc2

Browse files
authored
fix(sidebar): keep the flag-off layout unchanged, and cover it with a test
Two changes in this PR reached users who don't have the channels layout. The sidebar minimum went from 240 to 272 for everyone, but the extra room exists for the title bar's search button, which only the new layout renders — without it the strip needs 208px and fits 240 comfortably. Anyone on the old layout with a narrow sidebar would have been widened for nothing. SIDEBAR_MIN_WIDTH returns to 240 and the new layout raises its own floor: ResizableSidebar takes an optional minWidth, and ChannelsSidebar supplies it and lifts an already-narrower width once, only while the flag is on. The channel header's star also rendered unconditionally, so it appeared in the pre-existing channels alpha, which reaches that header too. It is now behind the flag like the rest of the new layout. Adds a test for openTaskInput, the riskiest shared change, pinning that creates go to Code (or an explicitly requested website space) until a channel is scoped, which only the new layout does. Generated-By: PostHog Code Task-Id: c401c7cb-e436-4da3-80bf-1d3008ee5d38
1 parent 3aa454e commit 0568bc2

6 files changed

Lines changed: 87 additions & 6 deletions

File tree

‎packages/ui/src/features/canvas/components/ChannelHeader.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function ChannelHeader({ channelId }: { channelId: string }) {
6969
{channelName ?? "Channel"}
7070
</Text>
7171
</Button>
72-
{channel && channel.name !== PERSONAL_CHANNEL_NAME && (
72+
{channelsLayout && channel && channel.name !== PERSONAL_CHANNEL_NAME && (
7373
<ChannelStarButton channel={channel} />
7474
)}
7575
{!channelsLayout && <ChannelTabs channelId={channelId} />}

‎packages/ui/src/features/canvas/components/ChannelsSidebar.tsx‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ProjectSwitcher } from "@posthog/ui/features/sidebar/components/Project
1818
import { SidebarMenu } from "@posthog/ui/features/sidebar/components/SidebarMenu";
1919
import { SidebarNavSection } from "@posthog/ui/features/sidebar/components/SidebarNavSection";
2020
import { UpdateBanner } from "@posthog/ui/features/sidebar/components/UpdateBanner";
21+
import { CHANNELS_SIDEBAR_MIN_WIDTH } from "@posthog/ui/features/sidebar/constants";
2122
import {
2223
beginSidebarPeek,
2324
cancelSidebarPeek,
@@ -90,6 +91,16 @@ export function ChannelsSidebar() {
9091
// mounts a provider-laden row per channel) swaps in.
9192
const bodyChannelsWorld = useDeferredValue(channelsWorld);
9293

94+
// The channels layout's title bar needs more room than the shared floor, so
95+
// it raises the minimum here rather than in the constant — leaving the width
96+
// of everyone on the old layout untouched.
97+
const minWidth = channelsLayout ? CHANNELS_SIDEBAR_MIN_WIDTH : undefined;
98+
useEffect(() => {
99+
if (channelsLayout && width < CHANNELS_SIDEBAR_MIN_WIDTH) {
100+
setWidth(CHANNELS_SIDEBAR_MIN_WIDTH);
101+
}
102+
}, [channelsLayout, width, setWidth]);
103+
93104
const archivedTaskIds = useArchivedTaskIds();
94105

95106
// The route is the source of truth: a /website/$channelId page adopts that
@@ -134,6 +145,7 @@ export function ChannelsSidebar() {
134145
isResizing={isResizing}
135146
setIsResizing={setIsResizing}
136147
side="left"
148+
minWidth={minWidth}
137149
setOpen={setOpen}
138150
peek={peek}
139151
onPeekEnter={beginSidebarPeek}

‎packages/ui/src/features/canvas/components/channelsSidebarStore.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const useChannelsSidebarStore = createSidebarStore({
55
name: "channels-sidebar",
66
defaultWidth: SIDEBAR_MIN_WIDTH,
77
// Also re-clamps the legacy Code width adopted by the migration below.
8+
// The channels layout raises the floor further, at runtime (ChannelsSidebar).
89
minWidth: SIDEBAR_MIN_WIDTH,
910
});
1011

‎packages/ui/src/features/sidebar/constants.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type { SidebarNavItem } from "@posthog/shared/analytics-events";
22

3+
export const SIDEBAR_MIN_WIDTH = 240;
4+
35
/**
4-
* The title bar's left strip is pinned to this width and none of its contents
5-
* shrink: inset, logo, toggle, search and history buttons come to 228px, so
6-
* anything near 240 leaves the two groups touching.
6+
* Wider floor for the channels layout only. The title bar's left strip is
7+
* pinned to the sidebar width and none of its contents shrink; that layout
8+
* adds a search button, taking the strip to 228px, so 240 would leave the two
9+
* groups touching. Off, the strip needs 208px and keeps the usual floor.
710
*/
8-
export const SIDEBAR_MIN_WIDTH = 272;
11+
export const CHANNELS_SIDEBAR_MIN_WIDTH = 272;
912

1013
export const CUSTOMIZABLE_NAV_ITEMS = [
1114
{ id: "inbox", label: "Inbox", analyticsId: "inbox", defaultVisible: true },

‎packages/ui/src/primitives/ResizableSidebar.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ interface ResizableSidebarProps {
2525
isResizing: boolean;
2626
setIsResizing: (isResizing: boolean) => void;
2727
side: "left" | "right";
28+
// Floor for drag-resize. Defaults to SIDEBAR_MIN_WIDTH; callers whose chrome
29+
// needs more room can raise it.
30+
minWidth?: number;
2831
// Enables drag-to-close/reopen. Without it, dragging just clamps at min.
2932
setOpen?: (open: boolean) => void;
3033
// While closed, the panel can "peek" — slide out over the content as a
@@ -45,6 +48,7 @@ export const ResizableSidebar: React.FC<ResizableSidebarProps> = ({
4548
isResizing,
4649
setIsResizing,
4750
side,
51+
minWidth = SIDEBAR_MIN_WIDTH,
4852
setOpen,
4953
peek = false,
5054
onPeekEnter,
@@ -101,7 +105,7 @@ export const ResizableSidebar: React.FC<ResizableSidebarProps> = ({
101105
const pointer =
102106
side === "left" ? e.clientX : window.innerWidth - e.clientX;
103107
const maxWidth = window.innerWidth * 0.5;
104-
const clamped = Math.max(SIDEBAR_MIN_WIDTH, Math.min(maxWidth, pointer));
108+
const clamped = Math.max(minWidth, Math.min(maxWidth, pointer));
105109

106110
if (open) {
107111
if (pointer < DRAG_COLLAPSE_AT && setOpen) {
@@ -166,6 +170,7 @@ export const ResizableSidebar: React.FC<ResizableSidebarProps> = ({
166170
isResizing,
167171
setIsResizing,
168172
side,
173+
minWidth,
169174
open,
170175
peek,
171176
width,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const navigateToChannelNewTask = vi.fn();
4+
const navigateToWebsiteNew = vi.fn();
5+
const navigateToCode = vi.fn();
6+
7+
vi.mock("./navigationBridge", () => ({
8+
navigateToChannelNewTask: (...args: unknown[]) =>
9+
navigateToChannelNewTask(...args),
10+
navigateToWebsiteNew: () => navigateToWebsiteNew(),
11+
navigateToCode: () => navigateToCode(),
12+
navigateToChannelTask: vi.fn(),
13+
navigateToTaskDetail: vi.fn(),
14+
navigateToFolderSettings: vi.fn(),
15+
}));
16+
vi.mock("@posthog/di/container", () => ({
17+
resolveService: vi.fn(),
18+
resolveServiceOptional: vi.fn(),
19+
}));
20+
vi.mock("@posthog/ui/shell/analytics", () => ({
21+
track: vi.fn(),
22+
setActiveTaskContext: vi.fn(),
23+
}));
24+
25+
import { useCurrentChannelStore } from "@posthog/ui/features/canvas/stores/currentChannelStore";
26+
import { openTaskInput } from "./useOpenTask";
27+
28+
describe("openTaskInput channel scoping", () => {
29+
beforeEach(() => {
30+
vi.clearAllMocks();
31+
useCurrentChannelStore.setState({ currentChannelId: null });
32+
});
33+
34+
// Without the channels layout nothing sets a current channel, so creates must
35+
// land where they always did rather than being pulled into a channel.
36+
it("routes to Code when no channel is current", () => {
37+
openTaskInput();
38+
expect(navigateToCode).toHaveBeenCalledTimes(1);
39+
expect(navigateToChannelNewTask).not.toHaveBeenCalled();
40+
});
41+
42+
it("still honours an explicit website space when no channel is current", () => {
43+
openTaskInput({ space: "website" });
44+
expect(navigateToWebsiteNew).toHaveBeenCalledTimes(1);
45+
expect(navigateToChannelNewTask).not.toHaveBeenCalled();
46+
});
47+
48+
it("routes into the current channel once one is scoped", () => {
49+
useCurrentChannelStore.setState({ currentChannelId: "chan-1" });
50+
openTaskInput();
51+
expect(navigateToChannelNewTask).toHaveBeenCalledWith("chan-1");
52+
expect(navigateToCode).not.toHaveBeenCalled();
53+
});
54+
55+
it("carries prefill into the channel route", () => {
56+
useCurrentChannelStore.setState({ currentChannelId: "chan-1" });
57+
openTaskInput({ initialPrompt: "ship it" });
58+
expect(navigateToChannelNewTask).toHaveBeenCalledWith("chan-1");
59+
});
60+
});

0 commit comments

Comments
 (0)