diff --git a/src/App.tsx b/src/App.tsx index 965d55d6..0884e4e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -381,7 +381,7 @@ function App() { {sidebarVisible && ( <>
- +
@@ -462,7 +462,7 @@ function App() { /> )} - handleSetSelection({ kind: 'filter', filter: 'changes' })} hasGitHub={!!settings.github_token} syncStatus={autoSync.syncStatus} lastSyncTime={autoSync.lastSyncTime} conflictCount={autoSync.conflictFiles.length} lastCommitInfo={autoSync.lastCommitInfo} remoteStatus={autoSync.remoteStatus} onTriggerSync={autoSync.triggerSync} onPullAndPush={autoSync.pullAndPush} onOpenConflictResolver={conflictFlow.handleOpenConflictResolver} zoomLevel={zoom.zoomLevel} onZoomReset={zoom.zoomReset} buildNumber={buildNumber} onCheckForUpdates={handleCheckForUpdates} onRemoveVault={vaultSwitcher.removeVault} mcpStatus={mcpStatus} onInstallMcp={installMcp} /> + handleSetSelection({ kind: 'filter', filter: 'changes' })} onClickPulse={() => handleSetSelection({ kind: 'filter', filter: 'pulse' })} onCommitPush={commitFlow.openCommitDialog} isGitVault={!vault.modifiedFilesError} hasGitHub={!!settings.github_token} syncStatus={autoSync.syncStatus} lastSyncTime={autoSync.lastSyncTime} conflictCount={autoSync.conflictFiles.length} lastCommitInfo={autoSync.lastCommitInfo} remoteStatus={autoSync.remoteStatus} onTriggerSync={autoSync.triggerSync} onPullAndPush={autoSync.pullAndPush} onOpenConflictResolver={conflictFlow.handleOpenConflictResolver} zoomLevel={zoom.zoomLevel} onZoomReset={zoom.zoomReset} buildNumber={buildNumber} onCheckForUpdates={handleCheckForUpdates} onRemoveVault={vaultSwitcher.removeVault} mcpStatus={mcpStatus} onInstallMcp={installMcp} /> setToastMessage(null)} /> diff --git a/src/components/Sidebar.test.tsx b/src/components/Sidebar.test.tsx index c6b617e0..6fcff34c 100644 --- a/src/components/Sidebar.test.tsx +++ b/src/components/Sidebar.test.tsx @@ -389,63 +389,11 @@ describe('Sidebar', () => { expect(screen.queryByTitle('New Project')).not.toBeInTheDocument() }) - it('renders commit button even when no modified files', () => { - render( {}} onCommitPush={() => {}} />) - expect(screen.getByText('Commit & Push')).toBeInTheDocument() - }) - - it('shows badge on commit button when modified files exist', () => { - render( {}} modifiedCount={3} onCommitPush={() => {}} />) - expect(screen.getByText('Commit & Push')).toBeInTheDocument() - const badges = screen.getAllByText('3') - expect(badges.length).toBeGreaterThanOrEqual(1) - }) - - it('shows Changes nav item when modifiedCount > 0', () => { - render( {}} modifiedCount={5} />) - expect(screen.getByText('Changes')).toBeInTheDocument() - }) - - it('hides Changes nav item when modifiedCount is 0', () => { - render( {}} modifiedCount={0} />) + it('does not render Changes or Pulse in sidebar', () => { + render( {}} />) expect(screen.queryByText('Changes')).not.toBeInTheDocument() - }) - - it('calls onSelect with changes filter when clicking Changes', () => { - const onSelect = vi.fn() - render() - fireEvent.click(screen.getByText('Changes')) - expect(onSelect).toHaveBeenCalledWith({ kind: 'filter', filter: 'changes' }) - }) - - describe('Changes and Pulse in secondary bottom area', () => { - it('renders Changes outside the main top nav section', () => { - render( {}} modifiedCount={3} isGitVault />) - const changesEl = screen.getByText('Changes') - // Changes should be inside the secondary bottom area, not the top nav - const secondaryArea = changesEl.closest('[data-testid="sidebar-secondary"]') - expect(secondaryArea).not.toBeNull() - }) - - it('renders Pulse outside the main top nav section', () => { - render( {}} isGitVault />) - const pulseEl = screen.getByText('Pulse') - const secondaryArea = pulseEl.closest('[data-testid="sidebar-secondary"]') - expect(secondaryArea).not.toBeNull() - }) - - it('does not render Changes or Pulse inside the top nav section', () => { - render( {}} modifiedCount={3} isGitVault />) - const topNav = screen.getByTestId('sidebar-top-nav') - expect(topNav.textContent).not.toContain('Changes') - expect(topNav.textContent).not.toContain('Pulse') - }) - - it('shows Changes badge count in secondary area', () => { - render( {}} modifiedCount={7} isGitVault />) - const secondaryArea = screen.getByTestId('sidebar-secondary') - expect(secondaryArea.textContent).toContain('7') - }) + expect(screen.queryByText('Pulse')).not.toBeInTheDocument() + expect(screen.queryByText('Commit & Push')).not.toBeInTheDocument() }) describe('dynamic custom type sections', () => { diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index eae15b62..0955d954 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -12,9 +12,9 @@ import { } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { - FileText, Trash, Archive, CaretLeft, GitDiff, Pulse, Tray, + FileText, Trash, Archive, CaretLeft, Tray, } from '@phosphor-icons/react' -import { GitCommitHorizontal, SlidersHorizontal } from 'lucide-react' +import { SlidersHorizontal } from 'lucide-react' import { type SectionGroup, isSelectionActive, NavItem, SectionContent, type SectionContentProps, VisibilityPopover, @@ -33,11 +33,8 @@ interface SidebarProps { onReorderSections?: (orderedTypes: { typeName: string; order: number }[]) => void onRenameSection?: (typeName: string, label: string) => void onToggleTypeVisibility?: (typeName: string) => void - modifiedCount?: number inboxCount?: number - onCommitPush?: () => void onCollapse?: () => void - isGitVault?: boolean } // --- Hooks --- @@ -140,21 +137,6 @@ function SortableSection({ group, sectionProps }: { ) } -function CommitButton({ modifiedCount, onClick }: { modifiedCount: number; onClick?: () => void }) { - if (!onClick) return null - return ( -
- -
- ) -} - function SidebarTitleBar({ onCollapse }: { onCollapse?: () => void }) { const { onMouseDown } = useDragRegion() return ( @@ -223,7 +205,7 @@ export const Sidebar = memo(function Sidebar({ entries, selection, onSelect, onSelectNote, onCreateType, onCreateNewType, onCustomizeType, onUpdateTypeTemplate, onReorderSections, onRenameSection, onToggleTypeVisibility, - modifiedCount = 0, inboxCount = 0, onCommitPush, onCollapse, isGitVault = false, + inboxCount = 0, onCollapse, }: SidebarProps) { const [collapsed, setCollapsed] = useState>({}) const [customizeTarget, setCustomizeTarget] = useState(null) @@ -333,14 +315,6 @@ export const Sidebar = memo(function Sidebar({ - {/* Secondary area: Changes + Pulse */} -
- {modifiedCount > 0 && ( - onSelect({ kind: 'filter', filter: 'changes' })} compact /> - )} - onSelect({ kind: 'filter', filter: 'pulse' }) : undefined} compact /> -
- { closeContextMenu(); setCustomizeTarget(type) }} onStartRename={handleStartRename} /> diff --git a/src/components/StatusBar.test.tsx b/src/components/StatusBar.test.tsx index bc6d32cc..0b73ebe6 100644 --- a/src/components/StatusBar.test.tsx +++ b/src/components/StatusBar.test.tsx @@ -182,18 +182,19 @@ describe('StatusBar', () => { expect(screen.getByText('Connect GitHub repo')).toBeInTheDocument() }) - it('shows modified count when modifiedCount is > 0', () => { + it('shows Changes badge with count when modifiedCount is > 0', () => { render() expect(screen.getByTestId('status-modified-count')).toBeInTheDocument() - expect(screen.getByText('3 pending')).toBeInTheDocument() + expect(screen.getByText('Changes')).toBeInTheDocument() + expect(screen.getByText('3')).toBeInTheDocument() }) - it('does not show modified count when modifiedCount is 0', () => { + it('does not show Changes badge when modifiedCount is 0', () => { render() expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument() }) - it('does not show modified count when modifiedCount is not provided', () => { + it('does not show Changes badge when modifiedCount is not provided', () => { render() expect(screen.queryByTestId('status-modified-count')).not.toBeInTheDocument() }) @@ -313,4 +314,37 @@ describe('StatusBar', () => { expect(screen.getByText(/1 behind/)).toBeInTheDocument() }) + it('shows Pulse badge in status bar', () => { + render() + expect(screen.getByTestId('status-pulse')).toBeInTheDocument() + expect(screen.getByText('Pulse')).toBeInTheDocument() + }) + + it('calls onClickPulse when clicking Pulse badge', () => { + const onClickPulse = vi.fn() + render() + fireEvent.click(screen.getByTestId('status-pulse')) + expect(onClickPulse).toHaveBeenCalledOnce() + }) + + it('disables Pulse badge when isGitVault is false', () => { + const onClickPulse = vi.fn() + render() + fireEvent.click(screen.getByTestId('status-pulse')) + expect(onClickPulse).not.toHaveBeenCalled() + }) + + it('shows Commit & Push button next to Changes badge', () => { + const onCommitPush = vi.fn() + render() + expect(screen.getByTestId('status-commit-push')).toBeInTheDocument() + fireEvent.click(screen.getByTestId('status-commit-push')) + expect(onCommitPush).toHaveBeenCalledOnce() + }) + + it('hides Commit & Push button when no modified files', () => { + render() + expect(screen.queryByTestId('status-commit-push')).not.toBeInTheDocument() + }) + }) diff --git a/src/components/StatusBar.tsx b/src/components/StatusBar.tsx index 23b5946e..82697604 100644 --- a/src/components/StatusBar.tsx +++ b/src/components/StatusBar.tsx @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from 'react' -import { Package, RefreshCw, FileText, Bell, Settings, FolderOpen, Check, Github, CircleDot, AlertTriangle, Loader2, GitCommitHorizontal, X, Cpu, ArrowDown, GitBranch } from 'lucide-react' +import { Package, RefreshCw, FileText, Bell, Settings, FolderOpen, Check, Github, AlertTriangle, Loader2, GitCommitHorizontal, X, Cpu, ArrowDown, GitBranch } from 'lucide-react' +import { GitDiff, Pulse } from '@phosphor-icons/react' import type { GitRemoteStatus, LastCommitInfo, SyncStatus } from '../types' import type { McpStatus } from '../hooks/useMcpStatus' import { openExternalUrl } from '../utils/url' @@ -20,6 +21,9 @@ interface StatusBarProps { onOpenLocalFolder?: () => void onConnectGitHub?: () => void onClickPending?: () => void + onClickPulse?: () => void + onCommitPush?: () => void + isGitVault?: boolean hasGitHub?: boolean syncStatus?: SyncStatus lastSyncTime?: number | null @@ -328,7 +332,7 @@ function ConflictBadge({ count, onClick }: { count: number; onClick?: () => void ) } -function PendingBadge({ count, onClick }: { count: number; onClick?: () => void }) { +function ChangesBadge({ count, onClick, onCommitPush }: { count: number; onClick?: () => void; onCommitPush?: () => void }) { if (count <= 0) return null return ( <> @@ -341,7 +345,50 @@ function PendingBadge({ count, onClick }: { count: number; onClick?: () => void onMouseEnter={e => { e.currentTarget.style.background = 'var(--hover)' }} onMouseLeave={e => { e.currentTarget.style.background = 'transparent' }} data-testid="status-modified-count" - >{count} pending + > + + {count} + Changes + + {onCommitPush && ( + { e.currentTarget.style.background = 'var(--hover)' }} + onMouseLeave={e => { e.currentTarget.style.background = 'transparent' }} + data-testid="status-commit-push" + > + + + )} + + ) +} + +function PulseBadge({ onClick, disabled }: { onClick?: () => void; disabled?: boolean }) { + return ( + <> + | + { e.currentTarget.style.background = 'var(--hover)' }} + onMouseLeave={disabled ? undefined : (e) => { e.currentTarget.style.background = 'transparent' }} + data-testid="status-pulse" + > + Pulse + ) } @@ -381,7 +428,7 @@ function McpBadge({ status, onInstall }: { status: McpStatus; onInstall?: () => ) } -export function StatusBar({ noteCount, modifiedCount = 0, vaultPath, vaults, onSwitchVault, onOpenSettings, onOpenLocalFolder, onConnectGitHub, onClickPending, hasGitHub, syncStatus = 'idle', lastSyncTime = null, conflictCount = 0, lastCommitInfo, remoteStatus, onTriggerSync, onPullAndPush, onOpenConflictResolver, zoomLevel = 100, onZoomReset, buildNumber, onCheckForUpdates, onRemoveVault, mcpStatus, onInstallMcp }: StatusBarProps) { +export function StatusBar({ noteCount, modifiedCount = 0, vaultPath, vaults, onSwitchVault, onOpenSettings, onOpenLocalFolder, onConnectGitHub, onClickPending, onClickPulse, onCommitPush, isGitVault = false, hasGitHub, syncStatus = 'idle', lastSyncTime = null, conflictCount = 0, lastCommitInfo, remoteStatus, onTriggerSync, onPullAndPush, onOpenConflictResolver, zoomLevel = 100, onZoomReset, buildNumber, onCheckForUpdates, onRemoveVault, mcpStatus, onInstallMcp }: StatusBarProps) { const [, setTick] = useState(0) useEffect(() => { const id = setInterval(() => setTick((t) => t + 1), 30_000) @@ -406,7 +453,8 @@ export function StatusBar({ noteCount, modifiedCount = 0, vaultPath, vaults, onS {lastCommitInfo && } - + + {mcpStatus && }