({
collapsed,
onToggleCollapsed,
sortedWorkspacesByProject,
workspaceRecency,
})
| 692 | } |
| 693 | |
| 694 | const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({ |
| 695 | collapsed, |
| 696 | onToggleCollapsed, |
| 697 | sortedWorkspacesByProject, |
| 698 | workspaceRecency, |
| 699 | }) => { |
| 700 | // Use the narrow actions context — does NOT subscribe to workspaceMetadata |
| 701 | // changes, preventing the entire sidebar tree from re-rendering on every |
| 702 | // workspace create/archive/rename. |
| 703 | const { |
| 704 | selectedWorkspace, |
| 705 | setSelectedWorkspace: onSelectWorkspace, |
| 706 | preflightArchiveWorkspace, |
| 707 | archiveWorkspace: onArchiveWorkspace, |
| 708 | removeWorkspace, |
| 709 | updateWorkspaceTitle: onUpdateTitle, |
| 710 | refreshWorkspaceMetadata, |
| 711 | pendingNewWorkspaceProject, |
| 712 | pendingNewWorkspaceDraftId, |
| 713 | workspaceDraftsByProject, |
| 714 | workspaceDraftPromotionsByProject, |
| 715 | createWorkspaceDraft, |
| 716 | openWorkspaceDraft, |
| 717 | deleteWorkspaceDraft, |
| 718 | } = useWorkspaceActions(); |
| 719 | const workspaceStore = useWorkspaceStoreRaw(); |
| 720 | useWorkspaceAttentionSubscription(sortedWorkspacesByProject, workspaceStore); |
| 721 | const runtimeStatusStore = useRuntimeStatusStoreRaw(); |
| 722 | const { navigateToProject } = useRouter(); |
| 723 | const { api } = useAPI(); |
| 724 | const preserveSubagentsUntilArchive = usePreserveSubagentsUntilArchiveSetting(api); |
| 725 | const { confirm: confirmDialog } = useConfirmDialog(); |
| 726 | const settings = useSettings(); |
| 727 | |
| 728 | // Get project state and operations from context |
| 729 | const { |
| 730 | userProjects, |
| 731 | openProjectCreateModal: onAddProject, |
| 732 | removeProject: onRemoveProject, |
| 733 | updateDisplayName, |
| 734 | updateColor: updateProjectColor, |
| 735 | assignWorkspaceToSubProject, |
| 736 | } = useProjectContext(); |
| 737 | |
| 738 | // Theme for logo variant |
| 739 | const { theme } = useTheme(); |
| 740 | const MuxLogo = theme === "dark" || theme.endsWith("-dark") ? MuxLogoDark : MuxLogoLight; |
| 741 | const multiProjectWorkspacesEnabled = useExperimentValue(EXPERIMENT_IDS.MULTI_PROJECT_WORKSPACES); |
| 742 | |
| 743 | // Mobile breakpoint for auto-closing sidebar |
| 744 | const MOBILE_BREAKPOINT = 768; |
| 745 | const projectListScrollRef = useRef<HTMLDivElement | null>(null); |
| 746 | const mobileScrollTopRef = useRef(0); |
| 747 | const wasCollapsedRef = useRef(collapsed); |
| 748 | |
| 749 | const normalizeMobileScrollTop = useCallback((scrollTop: number): number => { |
| 750 | return Number.isFinite(scrollTop) ? Math.max(0, Math.round(scrollTop)) : 0; |
| 751 | }, []); |
nothing calls this directly
no test coverage detected