fix: correct log order sorting and add configurable log order setting (#1720)

* feat: add configurable log order setting for task detail view

Add a new "Log Order" setting in Display Settings that allows users to
choose how logs are displayed in the task detail view:
- Chronological (oldest first): Oldest logs appear at top, auto-scroll to bottom
- Reverse-chronological (newest first): Newest logs appear at top, auto-scroll to top

Changes:
- Add logOrder property to AppSettings type ('chronological' | 'reverse-chronological')
- Set default value to 'chronological' to maintain current behavior
- Add English and French i18n translations
- Add Select component in DisplaySettings UI
- Update TaskLogs component to apply log order
- Update scroll behavior in useTaskDetail hook based on log order

* fix: increase log order dropdown width to prevent text truncation

* fix: address PR review comments - reactive settings and memoized entries

- Add reactive settings access at top of useTaskDetail hook
- Update auto-scroll useEffect to include settings.logOrder in dependency array
- Update handleLogsScroll to use reactive settings for consistency
- Add useMemo to PhaseLogSection to avoid re-calculating sorted entries on every render

Fixes review comments from PR #1720

* refactor: use focused selectors for logOrder to avoid unnecessary re-renders

- Replace wide subscription to settings object with focused selector for logOrder
- In useTaskDetail: use logOrder selector instead of full settings object
- In TaskLogs PhaseLogSection: subscribe only to logOrder instead of entire settings
- This ensures components only re-render when logOrder specifically changes

* fix: correct log order sorting and improve timestamp display

This commit fixes inverted log order logic and improves UX for task logs.

Bug Fixes:
- Fix inverted log order sorting: chronological now correctly shows oldest
  entries first (entries are naturally chronological from append() in backend)
- Fix auto-scroll not triggering when new logs arrive by adding phaseLogs
  to useEffect dependency array

UX Improvements:
- Add max-height and internal scrolling to log order dropdown to prevent
  viewport expansion
- Change timestamp format to use system locale (toLocaleString) which
  displays date and time according to user's OS settings, making it more
  readable for European users who prefer 24-hour format

Files changed:
- TaskLogs.tsx: fix sorting logic, update timestamp formatting
- useTaskDetail.ts: add phaseLogs to auto-scroll dependency array
- DisplaySettings.tsx: add max-height to SelectContent

* fix: preserve log entry state when toggling log order

Use stable timestamp as React key instead of timestamp+index to prevent
component remounting when log order changes. This preserves the isExpanded
state for log detail views when users toggle between chronological and
reverse-chronological order.

Previously, the key included the array index which changed on reorder,
causing React to unmount and remount all LogEntry components, losing
any expanded detail view state.

---------

Co-authored-by: Andy <[email protected]>
This commit is contained in:
Burak
2026-02-09 12:31:35 +02:00
committed by StillKnotKnown
co-authored by Andy
parent ab0cd7b54b
commit 085afc9907
@@ -1,5 +1,6 @@
import { useState, useRef, useEffect, useCallback } from 'react';
import { useProjectStore } from '../../../stores/project-store';
import { useSettingsStore } from '../../../stores/settings-store';
import { checkTaskRunning, isIncompleteHumanReview, getTaskProgress, useTaskStore, loadTasks, hasRecentActivity } from '../../../stores/task-store';
import type { Task, TaskLogs, TaskLogPhase, WorktreeStatus, WorktreeDiff, MergeConflict, MergeStats, GitConflictInfo, ImageAttachment } from '../../../../shared/types';
@@ -93,6 +94,7 @@ export function useTaskDetail({ task }: UseTaskDetailOptions) {
const [isCreatingPR, setIsCreatingPR] = useState(false);
const selectedProject = useProjectStore((state) => state.getSelectedProject());
const logOrder = useSettingsStore(s => s.settings.logOrder);
const isRunning = task.status === 'in_progress';
// isActiveTask includes ai_review for stuck detection (CHANGELOG documents this feature)
const isActiveTask = task.status === 'in_progress' || task.status === 'ai_review';
@@ -131,19 +133,31 @@ export function useTaskDetail({ task }: UseTaskDetailOptions) {
return () => clearInterval(intervalId);
}, [task.id, isActiveTask]);
// Handle scroll events in logs to detect if user scrolled up
// Handle scroll events in logs to detect if user scrolled away from anchor
const handleLogsScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
const isNearBottom = target.scrollHeight - target.scrollTop - target.clientHeight < 100;
setIsUserScrolledUp(!isNearBottom);
const isReverseOrder = logOrder === 'reverse-chronological';
// Check distance from top for reverse order, bottom for chronological
const isAtAnchor = isReverseOrder
? target.scrollTop < 100
: target.scrollHeight - target.scrollTop - target.clientHeight < 100;
setIsUserScrolledUp(!isAtAnchor);
};
// Auto-scroll logs to bottom only if user hasn't scrolled up
// Auto-scroll logs to anchor (top for reverse, bottom for chronological) only if user hasn't scrolled away
useEffect(() => {
if (activeTab === 'logs' && logsEndRef.current && !isUserScrolledUp) {
logsEndRef.current.scrollIntoView({ behavior: 'smooth' });
const isReverseOrder = logOrder === 'reverse-chronological';
if (activeTab === 'logs' && !isUserScrolledUp) {
if (isReverseOrder && logsContainerRef.current) {
logsContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
} else if (!isReverseOrder && logsEndRef.current) {
logsEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}
}, [activeTab, isUserScrolledUp]);
}, [activeTab, isUserScrolledUp, logOrder, phaseLogs]);
// Reset scroll state when switching to logs tab
useEffect(() => {