import { Inbox, ListChecks, CircleDot, Target, LayoutDashboard, DollarSign, History, Search, SquarePen, Network, Boxes, Repeat, Layers, GitBranch, Package, Settings, FolderOpen, PanelLeftClose, PanelLeftOpen, Pin, AppWindow, MessagesSquare, GanttChartSquare, LayoutGrid, } from "lucide-react"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { SidebarSection } from "./SidebarSection"; import { SidebarNavItem } from "./SidebarNavItem"; import { SidebarAgents } from "./SidebarAgents"; import { SidebarProjects } from "./SidebarProjects"; import { SidebarStarredProjects } from "./SidebarStarredProjects"; import { useDialogActions } from "../context/DialogContext"; import { useCompany } from "../context/CompanyContext"; import { useSidebar } from "../context/SidebarContext"; import { attentionApi } from "../api/attention"; import { heartbeatsApi } from "../api/heartbeats"; import { instanceSettingsApi } from "../api/instanceSettings"; import { queryKeys } from "../lib/queryKeys"; import { attentionBadgeCount } from "../lib/attention"; import { useInboxBadge } from "../hooks/useInboxBadge"; import { usePublishSharedQueryData, useSharedPollingQuery } from "../hooks/useSharedPolling"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn, SIDEBAR_RAIL_HIDDEN_LABEL } from "../lib/utils"; import { PluginSlotOutlet } from "@/plugins/slots"; import { PluginLauncherOutlet } from "@/plugins/launchers"; import { SidebarCompanyMenu } from "./SidebarCompanyMenu"; export function Sidebar() { const { openNewIssue } = useDialogActions(); // Every labeled section is collapsible (session-scoped, default open) — // one policy across static nav groups and the data-driven sections. const [workOpen, setWorkOpen] = useState(true); const [companyOpen, setCompanyOpen] = useState(true); const { selectedCompanyId, selectedCompany } = useCompany(); const { isMobile, collapsed, collapseLocked, peeking, toggleCollapsed, setCollapsed } = useSidebar(); const rail = collapsed && !peeking; const inboxBadge = useInboxBadge(selectedCompanyId); const { data: experimentalSettings } = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: () => instanceSettingsApi.getExperimental(), }); const liveRunsQueryKey = queryKeys.liveRuns(selectedCompanyId!); const sharedLiveRuns = useSharedPollingQuery({ companyId: selectedCompanyId, resourceKey: "live-runs", queryKey: liveRunsQueryKey, enabled: !!selectedCompanyId, // Event-sourced via LiveUpdatesProvider (GitHub issue 9627) + reconnect reconcile — no // interval poll needed. Polling here also re-armed React Query's timer on // every live-event cache write, a major source of steady-state churn. refetchInterval: false, leaderOnly: true, }); const { data: liveRuns, dataUpdatedAt: liveRunsUpdatedAt } = useQuery({ queryKey: liveRunsQueryKey, queryFn: () => heartbeatsApi.liveRunsForCompany(selectedCompanyId!), enabled: sharedLiveRuns.enabled, refetchInterval: sharedLiveRuns.refetchInterval, }); usePublishSharedQueryData(sharedLiveRuns, liveRuns, liveRunsUpdatedAt); const liveRunCount = liveRuns?.length ?? 0; const showWorkspacesLink = experimentalSettings?.enableIsolatedWorkspaces === true; const showApps = experimentalSettings?.enableApps === true; const showPipelines = experimentalSettings?.enablePipelines === true; const showStatusCards = experimentalSettings?.enableStatusCards === true; const goalsLinkPending = experimentalSettings === undefined; const showGoalsLink = experimentalSettings?.enableGoalsSidebarLink === true; // Decisions (attention home) is an experimental surface (PAP-13481): the nav // item is hidden entirely until the flag is enabled (same no-flash pattern as // showWorkspacesLink — it defaults hidden, so no placeholder is needed). const showDecisions = experimentalSettings?.enableDecisions === true; const { data: attentionFeed } = useQuery({ queryKey: queryKeys.attention(selectedCompanyId!), queryFn: () => attentionApi.list(selectedCompanyId!), enabled: !!selectedCompanyId && showDecisions, refetchInterval: 60_000, }); const attentionCount = attentionBadgeCount(attentionFeed); const showCases = experimentalSettings?.enableCases === true; // Streamlined left navigation (top-level Projects link + starred children) is // now the standard product sidebar (PAP-12472). The former experimental // opt-out was retired; classic per-project collapsible mode is no longer // user-selectable. Kept as a constant so the classic branch below stays as a // documented reference until it is fully removed. Routes are unaffected. const streamlined = true; // Conference Room Chat flag (PAP-136/PAP-137): the Conference Room nav item // is a new surface, hidden entirely while the flag is off (same no-flash // pattern as showWorkspacesLink above). const conferenceRoomChatEnabled = experimentalSettings?.enableConferenceRoomChat === true; const pluginContext = { companyId: selectedCompanyId, companyPrefix: selectedCompany?.issuePrefix ?? null, }; return ( ); }