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 && }