diff --git a/packages/ui/src/features/inbox/components/ConfigureAgentsSection.tsx b/packages/ui/src/features/inbox/components/ConfigureAgentsSection.tsx index 9bb68dcf20..3f98471ae2 100644 --- a/packages/ui/src/features/inbox/components/ConfigureAgentsSection.tsx +++ b/packages/ui/src/features/inbox/components/ConfigureAgentsSection.tsx @@ -212,7 +212,8 @@ export function ConfigureAgentsSection() { description="External tools responders can read from. PostHog data is always available; this is everything else." > track(ANALYTICS_EVENTS.AGENTS_ACTION, { action_type: "open_mcp_servers", diff --git a/packages/ui/src/features/scouts/components/ScoutHelperSkillLinks.tsx b/packages/ui/src/features/scouts/components/ScoutHelperSkillLinks.tsx index d2a543025b..b7fe4d6f49 100644 --- a/packages/ui/src/features/scouts/components/ScoutHelperSkillLinks.tsx +++ b/packages/ui/src/features/scouts/components/ScoutHelperSkillLinks.tsx @@ -23,7 +23,8 @@ export function ScoutHelperSkillLinks({ surface }: { surface: ScoutSurface }) { {index > 0 ? " ยท " : null} { track(ANALYTICS_EVENTS.SCOUT_ACTION, { action_type: "open_helper_skill", diff --git a/packages/ui/src/features/settings/components/SettingsPageContent.tsx b/packages/ui/src/features/settings/components/SettingsPageContent.tsx new file mode 100644 index 0000000000..4afe68ab67 --- /dev/null +++ b/packages/ui/src/features/settings/components/SettingsPageContent.tsx @@ -0,0 +1,181 @@ +import { McpServersView } from "@posthog/ui/features/mcp-servers/components/McpServersView"; +import { AdvancedSettings } from "@posthog/ui/features/settings/sections/AdvancedSettings"; +import { AgentsSettings } from "@posthog/ui/features/settings/sections/AgentsSettings"; +import { ClaudeCodeSettings } from "@posthog/ui/features/settings/sections/ClaudeCodeSettings"; +import { DiscordSettings } from "@posthog/ui/features/settings/sections/DiscordSettings"; +import { EnvironmentsSettings } from "@posthog/ui/features/settings/sections/environments/EnvironmentsSettings"; +import { GeneralSettings } from "@posthog/ui/features/settings/sections/GeneralSettings"; +import { GitHubSettings } from "@posthog/ui/features/settings/sections/GitHubSettings"; +import { NotificationsSettings } from "@posthog/ui/features/settings/sections/NotificationsSettings"; +import { PersonalizationSettings } from "@posthog/ui/features/settings/sections/PersonalizationSettings"; +import { PlanUsageSettings } from "@posthog/ui/features/settings/sections/PlanUsageSettings"; +import { ShortcutsSettings } from "@posthog/ui/features/settings/sections/ShortcutsSettings"; +import { SignalSourcesSettings } from "@posthog/ui/features/settings/sections/SignalSourcesSettings"; +import { SlackSettings } from "@posthog/ui/features/settings/sections/SlackSettings"; +import { TerminalSettings } from "@posthog/ui/features/settings/sections/TerminalSettings"; +import { UpdatesSettings } from "@posthog/ui/features/settings/sections/UpdatesSettings"; +import { WorkspacesSettings } from "@posthog/ui/features/settings/sections/WorkspacesSettings"; +import { WorktreesSettings } from "@posthog/ui/features/settings/sections/worktrees/WorktreesSettings"; +import type { SettingsCategory } from "@posthog/ui/features/settings/types"; +import { SkillsView } from "@posthog/ui/features/skills/SkillsView"; +import { Box, Flex, ScrollArea, Text } from "@radix-ui/themes"; +import type { ComponentType, ReactNode } from "react"; + +const SETTINGS_PAGE_LAYOUT = { + CONTAINED: "contained", + FULL_BLEED: "full-bleed", +} as const; + +type SettingsPageLayout = + (typeof SETTINGS_PAGE_LAYOUT)[keyof typeof SETTINGS_PAGE_LAYOUT]; + +interface SettingsPageDefinition { + title: string; + component: ComponentType; + layout: SettingsPageLayout; +} + +function defineSettingsPage( + title: string, + component: ComponentType, + layout: SettingsPageLayout = SETTINGS_PAGE_LAYOUT.CONTAINED, +): SettingsPageDefinition { + return { title, component, layout }; +} + +const SETTINGS_PAGES: Record = { + general: defineSettingsPage("General", GeneralSettings), + notifications: defineSettingsPage("Notifications", NotificationsSettings), + "plan-usage": defineSettingsPage("Plan & usage", PlanUsageSettings), + workspaces: defineSettingsPage("Workspaces", WorkspacesSettings), + worktrees: defineSettingsPage("Worktrees", WorktreesSettings), + environments: defineSettingsPage("Environments", EnvironmentsSettings), + "cloud-environments": defineSettingsPage( + "Environments", + EnvironmentsSettings, + ), + agents: defineSettingsPage("Agents", AgentsSettings), + skills: defineSettingsPage( + "Skills", + SkillsView, + SETTINGS_PAGE_LAYOUT.FULL_BLEED, + ), + "mcp-servers": defineSettingsPage( + "MCP servers", + McpServersView, + SETTINGS_PAGE_LAYOUT.FULL_BLEED, + ), + personalization: defineSettingsPage( + "Personalization", + PersonalizationSettings, + ), + terminal: defineSettingsPage("Terminal", TerminalSettings), + "claude-code": defineSettingsPage("Claude Code", ClaudeCodeSettings), + shortcuts: defineSettingsPage("Shortcuts", ShortcutsSettings), + github: defineSettingsPage("GitHub", GitHubSettings), + slack: defineSettingsPage("Slack integration", SlackSettings), + discord: defineSettingsPage("Discord", DiscordSettings), + // Slack notification config lives in the dedicated Slack section; the Signals + // section links out to it rather than duplicating the controls. + signals: defineSettingsPage("Self-driving", () => ( + + )), + updates: defineSettingsPage("Updates", UpdatesSettings), + advanced: defineSettingsPage("Advanced", AdvancedSettings), +}; + +interface SettingsPageLayoutProps { + children: ReactNode; + formMode: boolean; + icon?: ReactNode; + title: string; +} + +function SettingsPageHeader({ + formMode, + icon, + title, + bordered = false, +}: Omit & { bordered?: boolean }) { + if (formMode) return null; + + return ( + + {icon && {icon}} + {title} + + ); +} + +function ContainedSettingsPageLayout({ + children, + formMode, + icon, + title, +}: SettingsPageLayoutProps) { + return ( + + + + + {children} + + + + ); +} + +function FullBleedSettingsPageLayout({ + children, + formMode, + icon, + title, +}: SettingsPageLayoutProps) { + return ( + + +
{children}
+
+ ); +} + +const SETTINGS_PAGE_LAYOUT_COMPONENTS: Record< + SettingsPageLayout, + ComponentType +> = { + [SETTINGS_PAGE_LAYOUT.CONTAINED]: ContainedSettingsPageLayout, + [SETTINGS_PAGE_LAYOUT.FULL_BLEED]: FullBleedSettingsPageLayout, +}; + +interface SettingsPageContentProps { + category: SettingsCategory; + formMode: boolean; + icon?: ReactNode; +} + +export function SettingsPageContent({ + category, + formMode, + icon, +}: SettingsPageContentProps) { + const page = SETTINGS_PAGES[category]; + const PageComponent = page.component; + const PageLayout = SETTINGS_PAGE_LAYOUT_COMPONENTS[page.layout]; + + return ( + + + + ); +} diff --git a/packages/ui/src/features/settings/components/SettingsPanel.tsx b/packages/ui/src/features/settings/components/SettingsPanel.tsx index 87f698a212..5949d40d34 100644 --- a/packages/ui/src/features/settings/components/SettingsPanel.tsx +++ b/packages/ui/src/features/settings/components/SettingsPanel.tsx @@ -11,7 +11,10 @@ import { GearSix, GithubLogo, Keyboard, + Lightbulb, Palette, + Plugs, + Robot, SignOut, SlackLogo, Terminal, @@ -19,6 +22,7 @@ import { TreeStructure, Wrench, } from "@phosphor-icons/react"; +import { MenuLabel } from "@posthog/quill"; import { BILLING_FLAG } from "@posthog/shared"; import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient"; import { useAuthStateValue } from "@posthog/ui/features/auth/store"; @@ -26,30 +30,16 @@ import { useLogoutMutation } from "@posthog/ui/features/auth/useAuthMutations"; import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser"; import { getUserInitials } from "@posthog/ui/features/auth/userInitials"; import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag"; +import { SettingsPageContent } from "@posthog/ui/features/settings/components/SettingsPageContent"; import { closeSettings } from "@posthog/ui/features/settings/hooks/useOpenSettings"; -import { AdvancedSettings } from "@posthog/ui/features/settings/sections/AdvancedSettings"; -import { ClaudeCodeSettings } from "@posthog/ui/features/settings/sections/ClaudeCodeSettings"; -import { DiscordSettings } from "@posthog/ui/features/settings/sections/DiscordSettings"; -import { EnvironmentsSettings } from "@posthog/ui/features/settings/sections/environments/EnvironmentsSettings"; -import { GeneralSettings } from "@posthog/ui/features/settings/sections/GeneralSettings"; -import { GitHubSettings } from "@posthog/ui/features/settings/sections/GitHubSettings"; -import { NotificationsSettings } from "@posthog/ui/features/settings/sections/NotificationsSettings"; -import { PersonalizationSettings } from "@posthog/ui/features/settings/sections/PersonalizationSettings"; -import { PlanUsageSettings } from "@posthog/ui/features/settings/sections/PlanUsageSettings"; -import { ShortcutsSettings } from "@posthog/ui/features/settings/sections/ShortcutsSettings"; -import { SignalSourcesSettings } from "@posthog/ui/features/settings/sections/SignalSourcesSettings"; -import { SlackSettings } from "@posthog/ui/features/settings/sections/SlackSettings"; -import { TerminalSettings } from "@posthog/ui/features/settings/sections/TerminalSettings"; -import { UpdatesSettings } from "@posthog/ui/features/settings/sections/UpdatesSettings"; -import { WorkspacesSettings } from "@posthog/ui/features/settings/sections/WorkspacesSettings"; -import { WorktreesSettings } from "@posthog/ui/features/settings/sections/worktrees/WorktreesSettings"; +import { getHiddenSettingsCategories } from "@posthog/ui/features/settings/settingsVisibility"; import { useSettingsPageStore } from "@posthog/ui/features/settings/stores/settingsPageStore"; import type { SettingsCategory } from "@posthog/ui/features/settings/types"; import { useSpendAnalysisEnabled } from "@posthog/ui/features/usage/useSpendAnalysisEnabled"; import * as nav from "@posthog/ui/router/navigationBridge"; import { useHostCapabilities } from "@posthog/ui/shell/useHostCapabilities"; -import { Avatar, Box, Flex, ScrollArea, Text } from "@radix-ui/themes"; -import { type ReactNode, useMemo } from "react"; +import { Avatar, Flex, ScrollArea, Text } from "@radix-ui/themes"; +import type { ReactNode } from "react"; import { useHotkeys } from "react-hotkeys-hook"; interface SidebarItem { @@ -59,82 +49,80 @@ interface SidebarItem { hasChevron?: boolean; } -const SIDEBAR_ITEMS: SidebarItem[] = [ - { id: "general", label: "General", icon: }, - { id: "notifications", label: "Notifications", icon: }, - { id: "plan-usage", label: "Plan & usage", icon: }, - { id: "workspaces", label: "Workspaces", icon: }, - { id: "worktrees", label: "Worktrees", icon: }, - { id: "environments", label: "Environments", icon: }, +interface SidebarGroup { + label: string; + items: SidebarItem[]; +} + +const SIDEBAR_GROUPS: SidebarGroup[] = [ + { + label: "Account", + items: [ + { id: "general", label: "General", icon: }, + { id: "notifications", label: "Notifications", icon: }, + { + id: "plan-usage", + label: "Plan & usage", + icon: , + }, + ], + }, + { + label: "Workspace", + items: [ + { id: "workspaces", label: "Workspaces", icon: }, + { + id: "worktrees", + label: "Worktrees", + icon: , + }, + { id: "environments", label: "Environments", icon: }, + ], + }, + { + label: "Configure", + items: [ + { id: "agents", label: "Agents", icon: }, + { id: "skills", label: "Skills", icon: }, + { id: "mcp-servers", label: "MCP servers", icon: }, + { id: "claude-code", label: "Claude Code", icon: }, + { + id: "signals", + label: "Self-driving", + icon: , + }, + ], + }, { - id: "personalization", - label: "Personalization", - icon: , + label: "Experience", + items: [ + { + id: "personalization", + label: "Personalization", + icon: , + }, + { id: "terminal", label: "Terminal", icon: }, + { id: "shortcuts", label: "Shortcuts", icon: }, + ], + }, + { + label: "Integrations", + items: [ + { id: "github", label: "GitHub", icon: }, + { id: "slack", label: "Slack", icon: }, + { id: "discord", label: "Discord", icon: }, + ], + }, + { + label: "Application", + items: [ + { id: "updates", label: "Updates", icon: }, + { id: "advanced", label: "Advanced", icon: }, + ], }, - { id: "terminal", label: "Terminal", icon: }, - { id: "claude-code", label: "Claude Code", icon: }, - { id: "shortcuts", label: "Shortcuts", icon: }, - { id: "github", label: "GitHub", icon: }, - { id: "slack", label: "Slack", icon: }, - { id: "discord", label: "Discord", icon: }, - { id: "signals", label: "Self-driving", icon: }, - { id: "updates", label: "Updates", icon: }, - { id: "advanced", label: "Advanced", icon: }, ]; -// Settings that only make sense with a local filesystem/host (local worktrees, -// terminal, the local `claude` CLI, the desktop app itself). Hidden on the -// cloud-only web host. -const LOCAL_ONLY_CATEGORIES: ReadonlySet = new Set([ - "workspaces", - "worktrees", - "terminal", - "claude-code", - "discord", - "updates", -]); - -const CATEGORY_TITLES: Record = { - general: "General", - notifications: "Notifications", - "plan-usage": "Plan & usage", - workspaces: "Workspaces", - worktrees: "Worktrees", - environments: "Environments", - "cloud-environments": "Environments", - personalization: "Personalization", - terminal: "Terminal", - "claude-code": "Claude Code", - shortcuts: "Shortcuts", - github: "GitHub", - slack: "Slack integration", - discord: "Discord", - signals: "Self-driving", - updates: "Updates", - advanced: "Advanced", -}; - -const CATEGORY_COMPONENTS: Record = { - general: GeneralSettings, - notifications: NotificationsSettings, - "plan-usage": PlanUsageSettings, - workspaces: WorkspacesSettings, - worktrees: WorktreesSettings, - environments: EnvironmentsSettings, - "cloud-environments": EnvironmentsSettings, - personalization: PersonalizationSettings, - terminal: TerminalSettings, - "claude-code": ClaudeCodeSettings, - shortcuts: ShortcutsSettings, - github: GitHubSettings, - slack: SlackSettings, - discord: DiscordSettings, - // Slack notification config lives in the dedicated Slack section; the Signals - // section links out to it rather than duplicating the controls. - signals: () => , - updates: UpdatesSettings, - advanced: AdvancedSettings, -}; +const SIDEBAR_ITEMS = SIDEBAR_GROUPS.flatMap((group) => group.items); export interface SettingsPanelProps { /** @@ -170,29 +158,28 @@ export function SettingsPanel({ const logoutMutation = useLogoutMutation(); const spendAnalysisEnabled = useSpendAnalysisEnabled(); - const sidebarItems = useMemo( - () => - SIDEBAR_ITEMS.filter((item) => { - if ( - item.id === "plan-usage" && - !billingEnabled && - !spendAnalysisEnabled - ) - return false; - if (!localWorkspaces && LOCAL_ONLY_CATEGORIES.has(item.id)) - return false; - return true; - }), - [billingEnabled, spendAnalysisEnabled, localWorkspaces], - ); + const hiddenCategories = getHiddenSettingsCategories({ + billingEnabled, + spendAnalysisEnabled, + localWorkspaces, + }); + const sidebarGroups = SIDEBAR_GROUPS.map((group) => ({ + ...group, + items: group.items.filter((item) => !hiddenCategories.has(item.id)), + })).filter((group) => group.items.length > 0); // Guard direct navigation (URL, deep link, programmatic openSettings) to a // category hidden on this host. Fall back to General so a hidden section is // never rendered. - const resolvedCategory: SettingsCategory = - !localWorkspaces && LOCAL_ONLY_CATEGORIES.has(activeCategory) - ? "general" - : activeCategory; + const resolvedCategory: SettingsCategory = hiddenCategories.has( + activeCategory, + ) + ? "general" + : activeCategory; + const activeSidebarCategory: SettingsCategory = + resolvedCategory === "cloud-environments" + ? "environments" + : resolvedCategory; useHotkeys("escape", close, { enabled: true, @@ -201,12 +188,8 @@ export function SettingsPanel({ preventDefault: true, }); - const ActiveComponent = CATEGORY_COMPONENTS[resolvedCategory]; - const activeCategoryIcon = SIDEBAR_ITEMS.find( - (item) => - item.id === resolvedCategory || - (item.id === "environments" && resolvedCategory === "cloud-environments"), + (item) => item.id === activeSidebarCategory, )?.icon; const initials = getUserInitials(user); @@ -246,21 +229,25 @@ export function SettingsPanel({ -
- {sidebarItems.map((item) => { - const isActive = - resolvedCategory === item.id || - (item.id === "environments" && - resolvedCategory === "cloud-environments"); - return ( - setCategory(item.id)} - /> - ); - })} +
+ {sidebarGroups.map((group) => ( +
+ + {group.label} + + {group.items.map((item) => { + const isActive = activeSidebarCategory === item.id; + return ( + setCategory(item.id)} + /> + ); + })} +
+ ))}
@@ -312,23 +299,11 @@ export function SettingsPanel({ fill="url(#settings-dot-pattern)" /> - - - - {!formMode && ( - - {activeCategoryIcon && ( - {activeCategoryIcon} - )} - - {CATEGORY_TITLES[resolvedCategory]} - - - )} - - - - +
diff --git a/packages/ui/src/features/settings/sections/AgentsSettings.tsx b/packages/ui/src/features/settings/sections/AgentsSettings.tsx new file mode 100644 index 0000000000..459e1fbdac --- /dev/null +++ b/packages/ui/src/features/settings/sections/AgentsSettings.tsx @@ -0,0 +1,5 @@ +import { ConfigureAgentsSection } from "@posthog/ui/features/inbox/components/ConfigureAgentsSection"; + +export function AgentsSettings() { + return ; +} diff --git a/packages/ui/src/features/settings/settingsVisibility.test.ts b/packages/ui/src/features/settings/settingsVisibility.test.ts new file mode 100644 index 0000000000..2bf877efd1 --- /dev/null +++ b/packages/ui/src/features/settings/settingsVisibility.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { getHiddenSettingsCategories } from "./settingsVisibility"; + +describe("getHiddenSettingsCategories", () => { + it.each([ + { + name: "shows all categories when every capability is available", + input: { + billingEnabled: true, + spendAnalysisEnabled: true, + localWorkspaces: true, + }, + expected: [], + }, + { + name: "hides plan and usage without billing or spend analysis", + input: { + billingEnabled: false, + spendAnalysisEnabled: false, + localWorkspaces: true, + }, + expected: ["plan-usage"], + }, + { + name: "hides host-specific categories without local workspaces", + input: { + billingEnabled: true, + spendAnalysisEnabled: true, + localWorkspaces: false, + }, + expected: [ + "workspaces", + "worktrees", + "terminal", + "claude-code", + "discord", + "updates", + ], + }, + ])("$name", ({ input, expected }) => { + expect([...getHiddenSettingsCategories(input)]).toEqual(expected); + }); +}); diff --git a/packages/ui/src/features/settings/settingsVisibility.ts b/packages/ui/src/features/settings/settingsVisibility.ts new file mode 100644 index 0000000000..dcba2a38b1 --- /dev/null +++ b/packages/ui/src/features/settings/settingsVisibility.ts @@ -0,0 +1,38 @@ +import type { SettingsCategory } from "@posthog/ui/features/settings/types"; + +// Settings that only make sense with a local filesystem/host (local worktrees, +// terminal, the local `claude` CLI, the desktop app itself). Hidden on the +// cloud-only web host. +const LOCAL_ONLY_CATEGORIES: ReadonlySet = new Set([ + "workspaces", + "worktrees", + "terminal", + "claude-code", + "discord", + "updates", +]); + +interface SettingsVisibility { + billingEnabled: boolean; + spendAnalysisEnabled: boolean; + localWorkspaces: boolean; +} + +export function getHiddenSettingsCategories({ + billingEnabled, + spendAnalysisEnabled, + localWorkspaces, +}: SettingsVisibility): ReadonlySet { + const hiddenCategories = new Set(); + + if (!billingEnabled && !spendAnalysisEnabled) { + hiddenCategories.add("plan-usage"); + } + if (!localWorkspaces) { + for (const category of LOCAL_ONLY_CATEGORIES) { + hiddenCategories.add(category); + } + } + + return hiddenCategories; +} diff --git a/packages/ui/src/features/settings/types.ts b/packages/ui/src/features/settings/types.ts index 325494103b..e549aba42f 100644 --- a/packages/ui/src/features/settings/types.ts +++ b/packages/ui/src/features/settings/types.ts @@ -6,6 +6,9 @@ export type SettingsCategory = | "worktrees" | "environments" | "cloud-environments" + | "agents" + | "skills" + | "mcp-servers" | "personalization" | "terminal" | "claude-code" @@ -25,6 +28,9 @@ export const SETTINGS_CATEGORIES: readonly SettingsCategory[] = [ "worktrees", "environments", "cloud-environments", + "agents", + "skills", + "mcp-servers", "personalization", "terminal", "claude-code", diff --git a/packages/ui/src/features/sidebar/components/SidebarNavSection.tsx b/packages/ui/src/features/sidebar/components/SidebarNavSection.tsx index c92edb805f..45b8039867 100644 --- a/packages/ui/src/features/sidebar/components/SidebarNavSection.tsx +++ b/packages/ui/src/features/sidebar/components/SidebarNavSection.tsx @@ -6,20 +6,16 @@ import { HOME_TAB_FLAG } from "@posthog/shared/constants"; import { useCommandCenterStore } from "@posthog/ui/features/command-center/commandCenterStore"; import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag"; import { useInboxAllReports } from "@posthog/ui/features/inbox/hooks/useInboxAllReports"; +import { openSettings } from "@posthog/ui/features/settings/hooks/useOpenSettings"; import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore"; import { useTasks } from "@posthog/ui/features/tasks/useTasks"; import { navigateToActivity, - navigateToAgents, navigateToCommandCenter, navigateToHome, navigateToInbox, - navigateToMcpServers, - navigateToSkills, navigateToWebsiteCommandCenter, navigateToWebsiteHome, - navigateToWebsiteMcpServers, - navigateToWebsiteSkills, } from "@posthog/ui/router/navigationBridge"; import { useAppView } from "@posthog/ui/router/useAppView"; import { openTaskInput } from "@posthog/ui/router/useOpenTask"; @@ -28,14 +24,12 @@ import { useCommandMenuStore } from "@posthog/ui/shell/commandMenuStore"; import { Box, Flex } from "@radix-ui/themes"; import { useRouterState } from "@tanstack/react-router"; import { ActivityItem } from "./items/ActivityItem"; -import { AgentsItem } from "./items/AgentsItem"; import { CommandCenterItem } from "./items/CommandCenterItem"; +import { ConfigureItem } from "./items/ConfigureItem"; import { HomeItem } from "./items/HomeItem"; import { InboxItem } from "./items/InboxItem"; -import { McpServersItem } from "./items/McpServersItem"; import { NewTaskItem } from "./items/NewTaskItem"; import { SearchItem } from "./items/SearchItem"; -import { SkillsItem } from "./items/SkillsItem"; const SIDEBAR_INBOX_REFETCH_INTERVAL_MS = 60_000; @@ -52,8 +46,8 @@ interface SidebarNavSectionProps { // and the Channels pane. It is fully self-contained โ€” every item's active // state, badge count, and click handler is wired here โ€” so it can be dropped // into either layout. In the Channels space, destinations with a /website -// mirror (Home, Skills, MCP servers, Command Center) stay in that space; -// Inbox, Agents and New task have no mirror yet and jump back to Code. +// mirror (Home and Command Center) stay in that space; Inbox and New task have +// no mirror yet and jump back to Code. Configure opens the shared settings UI. // Search opens the command menu in place. export function SidebarNavSection({ commandCenterActiveCount: providedActiveCount, @@ -72,18 +66,14 @@ export function SidebarNavSection({ // When this section renders inside the Channels space, the destinations that // have a /website mirror stay in that space; everything else (and the whole - // section in the Code space) uses the canonical routes. Inbox, Agents and - // New task have no mirror yet, so they intentionally jump back to Code. + // section in the Code space) uses the canonical routes. Inbox and New task + // have no mirror yet, so they intentionally jump back to Code. const inChannels = useRouterState({ select: (s) => s.location.pathname.startsWith("/website"), }); const goNewTask = () => openTaskInput(inChannels ? { space: "website" } : undefined); const goHome = inChannels ? navigateToWebsiteHome : navigateToHome; - const goSkills = inChannels ? navigateToWebsiteSkills : navigateToSkills; - const goMcpServers = inChannels - ? navigateToWebsiteMcpServers - : navigateToMcpServers; const goCommandCenter = inChannels ? navigateToWebsiteCommandCenter : navigateToCommandCenter; @@ -95,10 +85,7 @@ export function SidebarNavSection({ const isHomeViewActive = view.type === "home"; const isActivityActive = view.type === "activity"; const isInboxActive = view.type === "inbox"; - const isAgentsActive = view.type === "agents"; const isCommandCenterActive = view.type === "command-center"; - const isSkillsActive = view.type === "skills"; - const isMcpServersActive = view.type === "mcp-servers"; // Open pull requests in the inbox โ€” the main CTA, and the same count the inbox // Pull requests tab shows, so the badge and the tab always agree. @@ -158,15 +145,7 @@ export function SidebarNavSection({ - - - - - - - - - + openSettings("agents")} /> diff --git a/packages/ui/src/features/sidebar/components/items/AgentsItem.tsx b/packages/ui/src/features/sidebar/components/items/AgentsItem.tsx deleted file mode 100644 index 7e935e2557..0000000000 --- a/packages/ui/src/features/sidebar/components/items/AgentsItem.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Robot } from "@phosphor-icons/react"; -import { SidebarItem } from "../SidebarItem"; - -interface AgentsItemProps { - isActive: boolean; - onClick: () => void; -} - -export function AgentsItem({ isActive, onClick }: AgentsItemProps) { - return ( - } - label="Agents" - isActive={isActive} - onClick={onClick} - /> - ); -} diff --git a/packages/ui/src/features/sidebar/components/items/ConfigureItem.tsx b/packages/ui/src/features/sidebar/components/items/ConfigureItem.tsx new file mode 100644 index 0000000000..c63562953b --- /dev/null +++ b/packages/ui/src/features/sidebar/components/items/ConfigureItem.tsx @@ -0,0 +1,17 @@ +import { SlidersHorizontal } from "@phosphor-icons/react"; +import { SidebarItem } from "../SidebarItem"; + +interface ConfigureItemProps { + onClick: () => void; +} + +export function ConfigureItem({ onClick }: ConfigureItemProps) { + return ( + } + label="Configure" + onClick={onClick} + /> + ); +} diff --git a/packages/ui/src/features/sidebar/components/items/McpServersItem.tsx b/packages/ui/src/features/sidebar/components/items/McpServersItem.tsx deleted file mode 100644 index f3f1146a0b..0000000000 --- a/packages/ui/src/features/sidebar/components/items/McpServersItem.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Plugs } from "@phosphor-icons/react"; -import { SidebarItem } from "../SidebarItem"; - -interface McpServersItemProps { - isActive: boolean; - onClick: () => void; -} - -export function McpServersItem({ isActive, onClick }: McpServersItemProps) { - return ( - } - label="MCP servers" - isActive={isActive} - onClick={onClick} - /> - ); -} diff --git a/packages/ui/src/features/sidebar/components/items/SkillsItem.tsx b/packages/ui/src/features/sidebar/components/items/SkillsItem.tsx deleted file mode 100644 index 3794819d99..0000000000 --- a/packages/ui/src/features/sidebar/components/items/SkillsItem.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Lightbulb } from "@phosphor-icons/react"; -import { SidebarItem } from "../SidebarItem"; - -interface SkillsItemProps { - isActive: boolean; - onClick: () => void; -} - -export function SkillsItem({ isActive, onClick }: SkillsItemProps) { - return ( - } - label="Skills" - isActive={isActive} - onClick={onClick} - /> - ); -} diff --git a/packages/ui/src/router/navigationBridge.ts b/packages/ui/src/router/navigationBridge.ts index 1489badac8..c906c67f3d 100644 --- a/packages/ui/src/router/navigationBridge.ts +++ b/packages/ui/src/router/navigationBridge.ts @@ -155,10 +155,6 @@ export function navigateToScoutFindings(): void { void getRouterOrNull()?.navigate({ to: "/code/agents/scouts/findings" }); } -export function navigateToAgents(): void { - void getRouterOrNull()?.navigate({ to: "/code/agents" }); -} - export function navigateToApproval(requestId: string): void { void getRouterOrNull()?.navigate({ to: "/code/agents/applications/approvals", @@ -177,14 +173,6 @@ export function navigateToCommandCenter(): void { track(ANALYTICS_EVENTS.COMMAND_CENTER_VIEWED); } -export function navigateToSkills(): void { - void getRouterOrNull()?.navigate({ to: "/skills" }); -} - -export function navigateToMcpServers(): void { - void getRouterOrNull()?.navigate({ to: "/mcp-servers" }); -} - // Channels-space mirrors. These render the same shared views as their /code (or // top-level) counterparts but under /website, so navigating from the channels // sidebar keeps the channels chrome instead of switching back to Code. The @@ -203,14 +191,6 @@ export function navigateToCanvas(): void { void getRouterOrNull()?.navigate({ to: "/website" }); } -export function navigateToWebsiteSkills(): void { - void getRouterOrNull()?.navigate({ to: "/website/skills" }); -} - -export function navigateToWebsiteMcpServers(): void { - void getRouterOrNull()?.navigate({ to: "/website/mcp-servers" }); -} - export function navigateToWebsiteCommandCenter(): void { void getRouterOrNull()?.navigate({ to: "/website/command-center" }); // Parity with navigateToCommandCenter's analytics tracking. diff --git a/packages/ui/src/router/routes/mcp-servers.tsx b/packages/ui/src/router/routes/mcp-servers.tsx index 4c5ff42e90..69b2f20e0d 100644 --- a/packages/ui/src/router/routes/mcp-servers.tsx +++ b/packages/ui/src/router/routes/mcp-servers.tsx @@ -1,11 +1,11 @@ -import { McpServersView } from "@posthog/ui/features/mcp-servers/components/McpServersView"; -import { - AppPageSkeleton, - withRouteSkeleton, -} from "@posthog/ui/router/routeSkeletons"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/mcp-servers")({ - component: McpServersView, - ...withRouteSkeleton(AppPageSkeleton), + beforeLoad: () => { + throw redirect({ + to: "/settings/$category", + params: { category: "mcp-servers" }, + replace: true, + }); + }, }); diff --git a/packages/ui/src/router/routes/skills.tsx b/packages/ui/src/router/routes/skills.tsx index 79081df381..973ae90db4 100644 --- a/packages/ui/src/router/routes/skills.tsx +++ b/packages/ui/src/router/routes/skills.tsx @@ -1,11 +1,11 @@ -import { SkillsView } from "@posthog/ui/features/skills/SkillsView"; -import { - AppPageSkeleton, - withRouteSkeleton, -} from "@posthog/ui/router/routeSkeletons"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/skills")({ - component: SkillsView, - ...withRouteSkeleton(AppPageSkeleton), + beforeLoad: () => { + throw redirect({ + to: "/settings/$category", + params: { category: "skills" }, + replace: true, + }); + }, }); diff --git a/packages/ui/src/router/routes/website/mcp-servers.tsx b/packages/ui/src/router/routes/website/mcp-servers.tsx index 6c99cefc7e..ac8cccef19 100644 --- a/packages/ui/src/router/routes/website/mcp-servers.tsx +++ b/packages/ui/src/router/routes/website/mcp-servers.tsx @@ -1,9 +1,11 @@ -import { McpServersView } from "@posthog/ui/features/mcp-servers/components/McpServersView"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; -// Channels-space mirror of /mcp-servers. Renders the same shared McpServersView -// so the page stays single-source; only the route entry is duplicated so -// navigating here keeps the channels chrome (rail + channel sidebar). export const Route = createFileRoute("/website/mcp-servers")({ - component: McpServersView, + beforeLoad: () => { + throw redirect({ + to: "/settings/$category", + params: { category: "mcp-servers" }, + replace: true, + }); + }, }); diff --git a/packages/ui/src/router/routes/website/skills.tsx b/packages/ui/src/router/routes/website/skills.tsx index e18f14d5e8..c01c82aa6f 100644 --- a/packages/ui/src/router/routes/website/skills.tsx +++ b/packages/ui/src/router/routes/website/skills.tsx @@ -1,9 +1,11 @@ -import { SkillsView } from "@posthog/ui/features/skills/SkillsView"; -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; -// Channels-space mirror of /skills. Renders the same shared SkillsView so the -// page stays single-source; only the route entry is duplicated so navigating -// here keeps the channels chrome (rail + channel sidebar). export const Route = createFileRoute("/website/skills")({ - component: SkillsView, + beforeLoad: () => { + throw redirect({ + to: "/settings/$category", + params: { category: "skills" }, + replace: true, + }); + }, });