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

Commit 7f1dbba

Browse files
authored
Simplify configuration and sidebar navigation (#3680)
1 parent f1563f9 commit 7f1dbba

13 files changed

Lines changed: 190 additions & 428 deletions

packages/ui/src/features/settings/components/SettingsPageContent.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { UpdatesSettings } from "@posthog/ui/features/settings/sections/UpdatesS
1717
import { WorkspacesSettings } from "@posthog/ui/features/settings/sections/WorkspacesSettings";
1818
import { WorktreesSettings } from "@posthog/ui/features/settings/sections/worktrees/WorktreesSettings";
1919
import type { SettingsCategory } from "@posthog/ui/features/settings/types";
20+
import { CustomizeSidebarSettings } from "@posthog/ui/features/sidebar/components/CustomizeSidebarDialog";
2021
import { SkillsView } from "@posthog/ui/features/skills/SkillsView";
2122
import { Box, Flex, ScrollArea, Text } from "@radix-ui/themes";
2223
import type { ComponentType, ReactNode } from "react";
@@ -69,6 +70,7 @@ const SETTINGS_PAGES: Record<SettingsCategory, SettingsPageDefinition> = {
6970
"Personalization",
7071
PersonalizationSettings,
7172
),
73+
sidebar: defineSettingsPage("Sidebar", CustomizeSidebarSettings),
7274
terminal: defineSettingsPage("Terminal", TerminalSettings),
7375
"claude-code": defineSettingsPage("Claude Code", ClaudeCodeSettings),
7476
shortcuts: defineSettingsPage("Shortcuts", ShortcutsSettings),

packages/ui/src/features/settings/components/SettingsPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Palette,
1616
Plugs,
1717
Robot,
18+
SidebarSimple,
1819
SignOut,
1920
SlackLogo,
2021
Terminal,
@@ -101,6 +102,7 @@ const SIDEBAR_GROUPS: SidebarGroup[] = [
101102
label: "Personalization",
102103
icon: <Palette size={16} />,
103104
},
105+
{ id: "sidebar", label: "Sidebar", icon: <SidebarSimple size={16} /> },
104106
{ id: "terminal", label: "Terminal", icon: <Terminal size={16} /> },
105107
{ id: "shortcuts", label: "Shortcuts", icon: <Keyboard size={16} /> },
106108
],
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { describe, expect, it } from "vitest";
2+
import { isSettingsCategory } from "./types";
3+
4+
describe("isSettingsCategory", () => {
5+
it("recognizes sidebar experience settings", () => {
6+
expect(isSettingsCategory("sidebar")).toBe(true);
7+
});
8+
});

packages/ui/src/features/settings/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type SettingsCategory =
1010
| "skills"
1111
| "mcp-servers"
1212
| "personalization"
13+
| "sidebar"
1314
| "terminal"
1415
| "claude-code"
1516
| "shortcuts"
@@ -32,6 +33,7 @@ export const SETTINGS_CATEGORIES: readonly SettingsCategory[] = [
3233
"skills",
3334
"mcp-servers",
3435
"personalization",
36+
"sidebar",
3537
"terminal",
3638
"claude-code",
3739
"shortcuts",
Lines changed: 58 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { LOOPS_FLAG } from "@posthog/shared";
12
import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events";
23
import { Theme } from "@radix-ui/themes";
34
import { act, render, screen } from "@testing-library/react";
@@ -9,8 +10,9 @@ type CapturedDragEvent = {
910
canceled?: boolean;
1011
};
1112

12-
const { track, dndCapture } = vi.hoisted(() => ({
13+
const { track, dndCapture, featureFlags } = vi.hoisted(() => ({
1314
track: vi.fn(),
15+
featureFlags: new Map<string, boolean>(),
1416
dndCapture: {} as {
1517
onDragStart?: (event: CapturedDragEvent) => void;
1618
onDragOver?: (event: CapturedDragEvent) => void;
@@ -19,6 +21,9 @@ const { track, dndCapture } = vi.hoisted(() => ({
1921
}));
2022

2123
vi.mock("@posthog/ui/shell/analytics", () => ({ track }));
24+
vi.mock("@posthog/ui/features/feature-flags/useFeatureFlag", () => ({
25+
useFeatureFlag: (key: string) => featureFlags.get(key) ?? true,
26+
}));
2227
vi.mock("@dnd-kit/react", () => ({
2328
DragDropProvider: ({
2429
onDragStart,
@@ -45,32 +50,13 @@ vi.mock("@dnd-kit/react/sortable", () => ({
4550
}),
4651
}));
4752

48-
import {
49-
CUSTOMIZABLE_NAV_ITEM_IDS,
50-
type CustomizableNavItemId,
51-
} from "@posthog/ui/features/sidebar/constants";
5253
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
53-
import { CustomizeSidebarDialog } from "./CustomizeSidebarDialog";
54+
import { CustomizeSidebarSettings } from "./CustomizeSidebarDialog";
5455

55-
function availability(
56-
overrides: Partial<Record<CustomizableNavItemId, boolean>> = {},
57-
) {
58-
return {
59-
...(Object.fromEntries(
60-
CUSTOMIZABLE_NAV_ITEM_IDS.map((id) => [id, true]),
61-
) as Record<CustomizableNavItemId, boolean>),
62-
...overrides,
63-
};
64-
}
65-
66-
function renderDialog(available = availability()) {
56+
function renderSettings() {
6757
return render(
6858
<Theme>
69-
<CustomizeSidebarDialog
70-
open
71-
onOpenChange={vi.fn()}
72-
available={available}
73-
/>
59+
<CustomizeSidebarSettings />
7460
</Theme>,
7561
);
7662
}
@@ -107,132 +93,129 @@ function rowLabels() {
10793
.map((checkbox) => checkbox.closest("label")?.textContent);
10894
}
10995

110-
describe("CustomizeSidebarDialog", () => {
96+
describe("CustomizeSidebarSettings", () => {
11197
beforeEach(() => {
11298
track.mockReset();
99+
featureFlags.clear();
113100
useSidebarStore.setState({ navItemOverrides: {}, navItemOrder: [] });
114101
});
115102

103+
it("omits items whose features are unavailable", () => {
104+
featureFlags.set(LOOPS_FLAG, false);
105+
106+
renderSettings();
107+
108+
expect(
109+
screen.queryByRole("checkbox", { name: "Loops" }),
110+
).not.toBeInTheDocument();
111+
});
112+
116113
it("unchecking a visible item demotes it and tracks the change", async () => {
117114
const user = userEvent.setup();
118-
renderDialog();
115+
renderSettings();
119116

120-
await user.click(screen.getByRole("checkbox", { name: "MCP servers" }));
117+
await user.click(screen.getByRole("checkbox", { name: "Command Center" }));
121118

122-
expect(useSidebarStore.getState().navItemOverrides["mcp-servers"]).toBe(
119+
expect(useSidebarStore.getState().navItemOverrides["command-center"]).toBe(
123120
false,
124121
);
125122
expect(track).toHaveBeenCalledWith(ANALYTICS_EVENTS.SIDEBAR_CUSTOMIZED, {
126-
item: "mcp_servers",
123+
item: "command_center",
127124
visible: false,
128125
});
129126
});
130127

131128
it("checking a hidden item promotes it and tracks the change", async () => {
132129
const user = userEvent.setup();
133-
renderDialog();
130+
useSidebarStore.setState({ navItemOverrides: { inbox: false } });
131+
renderSettings();
134132

135-
await user.click(screen.getByRole("checkbox", { name: "Search" }));
133+
await user.click(screen.getByRole("checkbox", { name: "Inbox" }));
136134

137-
expect(useSidebarStore.getState().navItemOverrides.search).toBe(true);
135+
expect(useSidebarStore.getState().navItemOverrides.inbox).toBe(true);
138136
expect(track).toHaveBeenCalledWith(ANALYTICS_EVENTS.SIDEBAR_CUSTOMIZED, {
139-
item: "search",
137+
item: "inbox",
140138
visible: true,
141139
});
142140
});
143141

144-
it("omits items marked unavailable", () => {
145-
renderDialog(availability({ loops: false }));
146-
147-
expect(
148-
screen.queryByRole("checkbox", { name: "Loops" }),
149-
).not.toBeInTheDocument();
150-
expect(
151-
screen.getByRole("checkbox", { name: "Configure" }),
152-
).toBeInTheDocument();
153-
});
154-
155142
it("renders rows in the stored order", () => {
156-
useSidebarStore.setState({ navItemOrder: ["configure", "search"] });
157-
renderDialog();
143+
useSidebarStore.setState({ navItemOrder: ["configure", "inbox"] });
144+
renderSettings();
158145

159-
expect(rowLabels().slice(0, 2)).toEqual(["Configure", "Search"]);
146+
expect(rowLabels().slice(0, 2)).toEqual(["Configure", "Inbox"]);
160147
});
161148

162149
it("previews on dragover and persists only on drop", () => {
163-
renderDialog();
150+
renderSettings();
164151

165-
dragStart("skills");
166-
dragOver("skills", "search");
152+
dragStart("loops");
153+
dragOver("loops", "inbox");
167154

168-
expect(rowLabels()[0]).toBe("Skills");
155+
expect(rowLabels()[0]).toBe("Loops");
169156
expect(useSidebarStore.getState().navItemOrder).toEqual([]);
170157
expect(track).not.toHaveBeenCalled();
171158

172-
dragEnd("skills");
159+
dragEnd("loops");
173160

174161
expect(useSidebarStore.getState().navItemOrder).toEqual([
175-
"skills",
176-
"search",
177-
"inbox",
178-
"agents",
179162
"loops",
180-
"mcp-servers",
163+
"inbox",
181164
"command-center",
182165
"contexts",
183166
"activity",
184167
"configure",
185168
]);
186169
expect(track).toHaveBeenCalledWith(ANALYTICS_EVENTS.SIDEBAR_REORDERED, {
187-
item: "skills",
170+
item: "loops",
188171
to_index: 0,
189172
});
190173
});
191174

192175
it("ignores a repeated dragover for the same source and target", () => {
193-
renderDialog();
176+
renderSettings();
194177

195-
dragStart("skills");
196-
dragOver("skills", "search");
197-
dragOver("skills", "search");
178+
dragStart("loops");
179+
dragOver("loops", "inbox");
180+
dragOver("loops", "inbox");
198181

199-
expect(rowLabels()[0]).toBe("Skills");
182+
expect(rowLabels()[0]).toBe("Loops");
200183

201-
dragEnd("skills");
184+
dragEnd("loops");
202185

203-
expect(useSidebarStore.getState().navItemOrder[0]).toBe("skills");
186+
expect(useSidebarStore.getState().navItemOrder[0]).toBe("loops");
204187
});
205188

206189
it("a canceled drag drops the preview and leaves the store untouched", () => {
207-
renderDialog();
190+
renderSettings();
208191

209-
dragStart("skills");
210-
dragOver("skills", "search");
211-
dragEnd("skills", { cancel: true });
192+
dragStart("loops");
193+
dragOver("loops", "inbox");
194+
dragEnd("loops", { cancel: true });
212195

213-
expect(rowLabels()[0]).toBe("Search");
196+
expect(rowLabels()[0]).toBe("Inbox");
214197
expect(useSidebarStore.getState().navItemOrder).toEqual([]);
215198
expect(track).not.toHaveBeenCalled();
216199
});
217200

218201
it("a drop without movement neither persists nor tracks", () => {
219-
renderDialog();
202+
renderSettings();
220203

221-
dragStart("skills");
222-
dragEnd("skills");
204+
dragStart("loops");
205+
dragEnd("loops");
223206

224207
expect(useSidebarStore.getState().navItemOrder).toEqual([]);
225208
expect(track).not.toHaveBeenCalled();
226209
});
227210

228211
it("reset clears the stored order back to the default", async () => {
229212
const user = userEvent.setup();
230-
useSidebarStore.setState({ navItemOrder: ["loops", "search"] });
231-
renderDialog();
213+
useSidebarStore.setState({ navItemOrder: ["loops", "inbox"] });
214+
renderSettings();
232215

233216
await user.click(screen.getByRole("button", { name: "Reset" }));
234217

235218
expect(useSidebarStore.getState().navItemOrder).toEqual([]);
236-
expect(rowLabels()[0]).toBe("Search");
219+
expect(rowLabels()[0]).toBe("Inbox");
237220
});
238221
});

0 commit comments

Comments
 (0)