From 449374b79f9ba782b2f273a9afe8e973070e098d Mon Sep 17 00:00:00 2001 From: hugo Date: Tue, 17 Feb 2026 13:24:25 +0100 Subject: [PATCH] fix: optimize git log actions --- src/renderer/components/Panel/Panel.tsx | 42 +++++++++++----------- src/renderer/store/appStore.ts | 7 ++++ tests/renderer/components/Panel.test.tsx | 44 ++++++++++++++++++++++-- tests/renderer/store/appStore.test.ts | 6 ++++ 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/renderer/components/Panel/Panel.tsx b/src/renderer/components/Panel/Panel.tsx index e01a7d2..977c3c2 100644 --- a/src/renderer/components/Panel/Panel.tsx +++ b/src/renderer/components/Panel/Panel.tsx @@ -2,8 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useAppStore } from '../../store'; import './Panel.css'; -type PanelTab = 'tasks' | 'output' | 'git-log'; - function getPostRelativePath(createdAt: string, slug: string): string | null { const createdDate = new Date(createdAt); if (Number.isNaN(createdDate.getTime())) { @@ -36,8 +34,7 @@ function toRelativePath(absolutePath: string, projectPath: string): string { } export const Panel: React.FC = () => { - const { panelVisible, tasks, tabs, activeTabId, posts, media, activeProject } = useAppStore(); - const [activePanelTab, setActivePanelTab] = useState('tasks'); + const { panelVisible, panelActiveTab, setPanelActiveTab, tasks, tabs, activeTabId, posts, media, activeProject } = useAppStore(); const [gitLogLoading, setGitLogLoading] = useState(false); const [gitLogError, setGitLogError] = useState(null); const [gitLogTargetLabel, setGitLogTargetLabel] = useState(null); @@ -53,14 +50,17 @@ export const Panel: React.FC = () => { const recentTasks = tasks.slice(-10).reverse(); const activeEditorTab = useMemo(() => tabs.find((tab) => tab.id === activeTabId) ?? null, [tabs, activeTabId]); const canActivateGitLog = activeEditorTab?.type === 'post' || activeEditorTab?.type === 'media'; + const effectiveActivePanelTab = panelActiveTab === 'git-log' && !canActivateGitLog + ? 'tasks' + : panelActiveTab; useEffect(() => { - if (!canActivateGitLog && activePanelTab === 'git-log') { - setActivePanelTab('tasks'); + if (!panelVisible || effectiveActivePanelTab !== 'git-log') { + setGitLogLoading(false); + setGitLogError(null); + return; } - }, [canActivateGitLog, activePanelTab]); - useEffect(() => { const projectPath = activeProject?.dataPath; if (!projectPath || !activeEditorTab || (activeEditorTab.type !== 'post' && activeEditorTab.type !== 'media')) { setGitLogEntries([]); @@ -140,7 +140,7 @@ export const Panel: React.FC = () => { }; void loadFileHistory(); - }, [activeEditorTab, activeProject?.dataPath, posts, media]); + }, [panelVisible, effectiveActivePanelTab, activeEditorTab, activeProject?.dataPath, posts, media]); if (!panelVisible) { return null; @@ -153,30 +153,30 @@ export const Panel: React.FC = () => {
- {activePanelTab === 'tasks' && ( + {effectiveActivePanelTab === 'tasks' && ( recentTasks.length === 0 ? (
No recent tasks
) : ( @@ -230,11 +230,11 @@ export const Panel: React.FC = () => { ) )} - {activePanelTab === 'output' && ( + {effectiveActivePanelTab === 'output' && (
No output
)} - {activePanelTab === 'git-log' && ( + {effectiveActivePanelTab === 'git-log' && ( !canActivateGitLog ? (
Open a post or media editor to view git log
) : gitLogLoading ? ( diff --git a/src/renderer/store/appStore.ts b/src/renderer/store/appStore.ts index 161b673..729acd3 100644 --- a/src/renderer/store/appStore.ts +++ b/src/renderer/store/appStore.ts @@ -39,6 +39,7 @@ export type { DeleteReference, ConfirmDeleteDetails }; export type EditorMode = 'wysiwyg' | 'markdown' | 'preview'; export type GitDiffViewStyle = 'inline' | 'side-by-side'; +export type PanelTab = 'tasks' | 'output' | 'git-log'; export interface GitDiffPreferences { wordWrap: boolean; @@ -60,6 +61,7 @@ interface AppState { activeView: 'posts' | 'pages' | 'media' | 'settings' | 'tags' | 'chat' | 'import' | 'git'; sidebarVisible: boolean; panelVisible: boolean; + panelActiveTab: PanelTab; selectedPostId: string | null; selectedMediaId: string | null; preferredEditorMode: EditorMode; @@ -107,6 +109,7 @@ interface AppState { setActiveView: (view: 'posts' | 'pages' | 'media' | 'settings' | 'tags' | 'chat' | 'import' | 'git') => void; toggleSidebar: () => void; togglePanel: () => void; + setPanelActiveTab: (tab: PanelTab) => void; setSelectedPost: (id: string | null) => void; setSelectedMedia: (id: string | null) => void; setPreferredEditorMode: (mode: EditorMode) => void; @@ -159,6 +162,7 @@ export const useAppStore = create()( activeView: 'posts', sidebarVisible: true, panelVisible: false, + panelActiveTab: 'tasks', selectedPostId: null, selectedMediaId: null, preferredEditorMode: 'wysiwyg', @@ -281,6 +285,7 @@ export const useAppStore = create()( setActiveView: (view) => set({ activeView: view }), toggleSidebar: () => set((state) => ({ sidebarVisible: !state.sidebarVisible })), togglePanel: () => set((state) => ({ panelVisible: !state.panelVisible })), + setPanelActiveTab: (panelActiveTab) => set({ panelActiveTab }), setSelectedPost: (id) => set({ selectedPostId: id }), setSelectedMedia: (id) => set({ selectedMediaId: id }), setPreferredEditorMode: (mode) => set({ preferredEditorMode: mode }), @@ -367,6 +372,7 @@ export const useAppStore = create()( activeView: state.activeView, sidebarVisible: state.sidebarVisible, panelVisible: state.panelVisible, + panelActiveTab: state.panelActiveTab, selectedPostId: state.selectedPostId, selectedMediaId: state.selectedMediaId, preferredEditorMode: state.preferredEditorMode, @@ -385,6 +391,7 @@ export const useAppStore = create()( ...persistedState, tabs: persistedState.tabs || [], activeTabId: persistedState.activeTabId || null, + panelActiveTab: persistedState.panelActiveTab || current.panelActiveTab, dirtyPosts: new Set(persistedState.dirtyPosts || []), gitDiffPreferences: persistedState.gitDiffPreferences || current.gitDiffPreferences, }; diff --git a/tests/renderer/components/Panel.test.tsx b/tests/renderer/components/Panel.test.tsx index cec1d51..9755a5c 100644 --- a/tests/renderer/components/Panel.test.tsx +++ b/tests/renderer/components/Panel.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; -import { act, render, screen } from '@testing-library/react'; +import { act, render, screen, fireEvent } from '@testing-library/react'; import { Panel } from '../../../src/renderer/components/Panel/Panel'; import { useAppStore } from '../../../src/renderer/store'; import type { PostData, MediaData } from '../../../src/main/shared/electronApi'; @@ -54,6 +54,7 @@ describe('Panel', () => { useAppStore.setState({ panelVisible: true, + panelActiveTab: 'tasks', tasks: [], activeProject: { id: 'project-1', @@ -72,7 +73,7 @@ describe('Panel', () => { }); afterEach(() => { - useAppStore.setState({ panelVisible: false }); + useAppStore.setState({ panelVisible: false, panelActiveTab: 'tasks' }); }); it('renders a Git Log tab label instead of Sync Log', () => { @@ -107,6 +108,8 @@ describe('Panel', () => { render(); + fireEvent.click(screen.getByRole('tab', { name: 'Git Log' })); + await vi.waitFor(() => { expect(getFileHistory).toHaveBeenCalledWith('/repo/path', 'posts/2026/02/first-post.md', 50); }); @@ -123,6 +126,30 @@ describe('Panel', () => { }); }); + it('does not load git history when panel is closed', async () => { + const getFileHistory = vi.fn().mockResolvedValue([]); + (window as any).electronAPI.git.getFileHistory = getFileHistory; + + useAppStore.setState({ panelVisible: false, panelActiveTab: 'git-log' }); + + render(); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(getFileHistory).not.toHaveBeenCalled(); + }); + + it('does not load git history when Git Log tab is not active', async () => { + const getFileHistory = vi.fn().mockResolvedValue([]); + (window as any).electronAPI.git.getFileHistory = getFileHistory; + + useAppStore.setState({ panelActiveTab: 'tasks' }); + + render(); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(getFileHistory).not.toHaveBeenCalled(); + }); + it('disables Git Log tab when focused tab is not a post or media editor', () => { useAppStore.setState({ tabs: [{ type: 'settings', id: 'settings', isTransient: false }], @@ -133,4 +160,17 @@ describe('Panel', () => { expect(screen.getByRole('tab', { name: 'Git Log' })).toHaveAttribute('aria-disabled', 'true'); }); + + it('restores the last selected panel tab after remounting the panel', () => { + const firstRender = render(); + + fireEvent.click(screen.getByRole('tab', { name: 'Output' })); + expect(screen.getByRole('tab', { name: 'Output' })).toHaveAttribute('aria-selected', 'true'); + + firstRender.unmount(); + + render(); + + expect(screen.getByRole('tab', { name: 'Output' })).toHaveAttribute('aria-selected', 'true'); + }); }); diff --git a/tests/renderer/store/appStore.test.ts b/tests/renderer/store/appStore.test.ts index 5964884..1faa78e 100644 --- a/tests/renderer/store/appStore.test.ts +++ b/tests/renderer/store/appStore.test.ts @@ -167,6 +167,12 @@ describe('AppStore', () => { expect(getStore().preferredEditorMode).toBe('markdown'); }); + it('should set active panel tab', () => { + getStore().setPanelActiveTab('output'); + + expect(getStore().panelActiveTab).toBe('output'); + }); + it('should default git diff preferences to wrapped inline and visible unchanged regions', () => { expect(getStore().gitDiffPreferences).toEqual({ wordWrap: true,