From d203ba2ae0b162213c16e59fd30fc16c4e835ef7 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 18 Apr 2026 13:46:57 +0300 Subject: [PATCH] feat(sidebar): project notes/todos live in right sidebar context tab Replace the header sticky-note popover/mobile overlay with a dedicated Context tab in the right sidebar. Context is cycled by the right-sidebar shortcut alongside git and files, and persisted across reloads. --- .../components/layout/RightSidebarTabs.tsx | 68 +++++++++++++++++-- .../src/components/session/SessionSidebar.tsx | 64 ----------------- .../session/sidebar/SidebarHeader.tsx | 58 ---------------- packages/ui/src/hooks/useKeyboardShortcuts.ts | 2 +- packages/ui/src/stores/useUIStore.ts | 7 +- 5 files changed, 70 insertions(+), 129 deletions(-) diff --git a/packages/ui/src/components/layout/RightSidebarTabs.tsx b/packages/ui/src/components/layout/RightSidebarTabs.tsx index cc25c247..f636fd3b 100644 --- a/packages/ui/src/components/layout/RightSidebarTabs.tsx +++ b/packages/ui/src/components/layout/RightSidebarTabs.tsx @@ -1,15 +1,19 @@ import React from 'react'; -import { RiFolder3Line, RiGitBranchLine } from '@remixicon/react'; +import { RiBookletLine, RiFolder3Line, RiGitBranchLine } from '@remixicon/react'; import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip'; +import { ProjectNotesTodoPanel } from '@/components/session/ProjectNotesTodoPanel'; import { GitView } from '@/components/views'; -import { useUIStore } from '@/stores/useUIStore'; import { useGitStore } from '@/stores/useGitStore'; +import { useProjectsStore } from '@/stores/useProjectsStore'; +import { useDirectoryStore } from '@/stores/useDirectoryStore'; +import { useUIStore } from '@/stores/useUIStore'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; +import { formatDirectoryName } from '@/lib/utils'; import { SidebarFilesTree } from './SidebarFilesTree'; -type RightTab = 'git' | 'files'; +type RightTab = 'git' | 'files' | 'context'; /** * Keeps git status fresh while the right sidebar is open. @@ -35,6 +39,56 @@ function useRightSidebarGitSync(directory: string | undefined, isSidebarOpen: bo }, [directory, git, isSidebarOpen, ensureStatus]); } +const ContextSidebarPanel: React.FC = () => { + const activeProjectId = useProjectsStore((state) => state.activeProjectId); + const projects = useProjectsStore((state) => state.projects); + const homeDirectory = useDirectoryStore((state) => state.homeDirectory); + const gitDirectories = useGitStore((state) => state.directories); + + const activeProject = React.useMemo(() => { + if (activeProjectId) { + return projects.find((project) => project.id === activeProjectId) ?? projects[0] ?? null; + } + return projects[0] ?? null; + }, [activeProjectId, projects]); + + const projectRef = React.useMemo(() => { + if (!activeProject) { + return null; + } + return { + id: activeProject.id, + path: activeProject.path, + }; + }, [activeProject]); + + const projectLabel = React.useMemo(() => { + if (!activeProject) { + return null; + } + return activeProject.label?.trim() + || formatDirectoryName(activeProject.path, homeDirectory) + || activeProject.path; + }, [activeProject, homeDirectory]); + + const canCreateWorktree = React.useMemo(() => { + if (!activeProject) { + return false; + } + return gitDirectories.get(activeProject.path)?.isGitRepo === true; + }, [activeProject, gitDirectories]); + + return ( +
+ +
+ ); +}; + export const RightSidebarTabs: React.FC = () => { const rightSidebarTab = useUIStore((state) => state.rightSidebarTab); const setRightSidebarTab = useUIStore((state) => state.setRightSidebarTab); @@ -54,6 +108,11 @@ export const RightSidebarTabs: React.FC = () => { label: 'Files', icon: , }, + { + id: 'context', + label: 'Context', + icon: , + }, ], []); return ( @@ -72,7 +131,8 @@ export const RightSidebarTabs: React.FC = () => {
{rightSidebarTab === 'git' && } {rightSidebarTab === 'files' && } + {rightSidebarTab === 'context' && }
); -}; \ No newline at end of file +}; diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index fcbbf19c..dc8deafd 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -4,7 +4,6 @@ import { RiLayoutLeftLine } from '@remixicon/react'; import { toast } from '@/components/ui'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { isDesktopLocalOriginActive, isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop'; -import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel'; import { sessionEvents } from '@/lib/sessionEvents'; import { formatDirectoryName, cn } from '@/lib/utils'; import { useSessionUIStore } from '@/sync/session-ui-store'; @@ -16,11 +15,9 @@ import { useProjectsStore } from '@/stores/useProjectsStore'; import { useUIStore } from '@/stores/useUIStore'; import { getSafeStorage } from '@/stores/utils/safeStorage'; import { useGitStore, useGitAllBranches, useGitRepoStatusMap } from '@/stores/useGitStore'; -import { useDeviceInfo } from '@/lib/device'; import { isVSCodeRuntime } from '@/lib/desktop'; import { NewWorktreeDialog } from './NewWorktreeDialog'; import { ScheduledTasksDialog } from './ScheduledTasksDialog'; -import { ProjectNotesTodoPanel } from './ProjectNotesTodoPanel'; import { useSessionFoldersStore } from '@/stores/useSessionFoldersStore'; import { useDebouncedValue } from '@/hooks/useDebouncedValue'; import { useArchivedAutoFolders } from './sidebar/hooks/useArchivedAutoFolders'; @@ -142,7 +139,6 @@ export const SessionSidebar: React.FC = ({ const [expandedSessionGroups, setExpandedSessionGroups] = React.useState>(new Set()); const [newWorktreeDialogOpen, setNewWorktreeDialogOpen] = React.useState(false); const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); - const [projectNotesPanelOpen, setProjectNotesPanelOpen] = React.useState(false); const [openSidebarMenuKey, setOpenSidebarMenuKey] = React.useState(null); const [renamingFolderId, setRenamingFolderId] = React.useState(null); const [renameFolderDraft, setRenameFolderDraft] = React.useState(''); @@ -230,7 +226,6 @@ export const SessionSidebar: React.FC = ({ const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen); const toggleHelpDialog = useUIStore((state) => state.toggleHelpDialog); const setAboutDialogOpen = useUIStore((state) => state.setAboutDialogOpen); - const deviceInfo = useDeviceInfo(); const setSessionSwitcherOpen = useUIStore((state) => state.setSessionSwitcherOpen); const setScheduledTasksDialogOpen = useUIStore((state) => state.setScheduledTasksDialogOpen); const toggleSidebar = useUIStore((state) => state.toggleSidebar); @@ -893,44 +888,7 @@ export const SessionSidebar: React.FC = ({ ); - const activeProjectForHeader = React.useMemo( - () => normalizedProjects.find((project) => project.id === activeProjectId) ?? normalizedProjects[0] ?? null, - [normalizedProjects, activeProjectId], - ); - const activeProjectRefForHeader = React.useMemo( - () => (activeProjectForHeader - ? { - id: activeProjectForHeader.id, - path: activeProjectForHeader.normalizedPath, - } - : null), - [activeProjectForHeader], - ); - const activeProjectLabelForHeader = React.useMemo( - () => (activeProjectForHeader - ? activeProjectForHeader.label?.trim() - || formatDirectoryName(activeProjectForHeader.normalizedPath, homeDirectory) - || activeProjectForHeader.normalizedPath - : null), - [activeProjectForHeader, homeDirectory], - ); - - const activeProjectIsRepo = React.useMemo( - () => (activeProjectForHeader ? Boolean(projectRepoStatus.get(activeProjectForHeader.id)) : false), - [activeProjectForHeader, projectRepoStatus], - ); - // Only flip to false once the new project's status is actually resolved (present in map) - const stableActiveProjectIsRepo = activeProjectForHeader && projectRepoStatus.has(activeProjectForHeader.id) - ? activeProjectIsRepo - : lastRepoStatusRef.current; const reserveHeaderActionsSpace = true; - const useMobileNotesPanel = mobileVariant || deviceInfo.isMobile; - - React.useEffect(() => { - if (!activeProjectForHeader) { - setProjectNotesPanelOpen(false); - } - }, [activeProjectForHeader]); const { currentSessionDirectory } = useProjectSessionSelection({ projectSections, @@ -1479,14 +1437,8 @@ export const SessionSidebar: React.FC = ({ hideDirectoryControls={hideDirectoryControls} handleOpenDirectoryDialog={handleOpenDirectoryDialog} handleNewSession={handleSidebarNewSession} - useMobileNotesPanel={useMobileNotesPanel} - projectNotesPanelOpen={projectNotesPanelOpen} - setProjectNotesPanelOpen={setProjectNotesPanelOpen} - activeProjectRefForHeader={activeProjectRefForHeader} - activeProjectLabelForHeader={activeProjectLabelForHeader} canOpenMultiRun={projects.length > 0} openMultiRunLauncher={handleOpenMultiRunFromHeader} - stableActiveProjectIsRepo={stableActiveProjectIsRepo} headerActionIconClass={headerActionIconClass} reserveHeaderActionsSpace={reserveHeaderActionsSpace} headerActionButtonClass={headerActionButtonClass} @@ -1594,22 +1546,6 @@ export const SessionSidebar: React.FC = ({ - {useMobileNotesPanel ? ( - setProjectNotesPanelOpen(false)} - title={activeProjectLabelForHeader ? `Project notes - ${activeProjectLabelForHeader}` : 'Project notes'} - > - setProjectNotesPanelOpen(false)} - className="p-0" - /> - - ) : null} - void; handleNewSession: () => void; - useMobileNotesPanel: boolean; - projectNotesPanelOpen: boolean; - setProjectNotesPanelOpen: (open: boolean) => void; - activeProjectRefForHeader: ProjectRef | null; - activeProjectLabelForHeader: string | null; canOpenMultiRun: boolean; openMultiRunLauncher: () => void; - stableActiveProjectIsRepo: boolean; headerActionIconClass: string; reserveHeaderActionsSpace: boolean; headerActionButtonClass: string; @@ -56,14 +47,8 @@ export function SidebarHeader(props: Props): React.ReactNode { hideDirectoryControls, handleOpenDirectoryDialog, handleNewSession, - useMobileNotesPanel, - projectNotesPanelOpen, - setProjectNotesPanelOpen, - activeProjectRefForHeader, - activeProjectLabelForHeader, canOpenMultiRun, openMultiRunLauncher, - stableActiveProjectIsRepo, headerActionIconClass, reserveHeaderActionsSpace, headerActionButtonClass, @@ -150,49 +135,6 @@ export function SidebarHeader(props: Props): React.ReactNode {

Scheduled tasks

- {useMobileNotesPanel ? ( - - - - -

Project notes

-
- ) : ( - - - - - - - -

Project notes

-
- - setProjectNotesPanelOpen(false)} - /> - -
- )} -