From 21898a331ef5b765b6483de5157a90a97650c608 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Tue, 9 Dec 2025 10:18:18 -0600 Subject: [PATCH] feat: History panel enhancements - navigation, search, scroll persistence, styling - Add Prev/Next navigation buttons to HistoryDetailModal with left/right arrow key bindings - Filter-aware navigation (respects AUTO/USER visibility settings) - Add session ID search capability (long and short octet form) - Persist scroll position across session switches using module-level cache - Fix random scroll-to-top on external refresh by preserving state during refresh - Enhanced validated entry styling (solid green background, white checkmarks) - Activity graph now supports configurable lookback via right-click context menu (24h, 72h, 1 week, 2 weeks, 1 month, 6 months, 1 year, all time) Claude ID: e2360cc6-54db-411f-9b53-c39ee8212c2a Maestro ID: b9bc0d08-5be2-4fdf-93cd-5618a8d53b35 --- .gitignore | 3 + README.md | 21 ++++--- src/renderer/components/HistoryPanel.tsx | 70 +++++++++++++++++++----- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 9d71a2eb..864d39ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Maestro +Auto\ Run\ Docs + # Tests coverage/ do-wishlist.sh diff --git a/README.md b/README.md index a8e5a010..d471e8a3 100644 --- a/README.md +++ b/README.md @@ -35,21 +35,21 @@ NOTE: On macOS you may need to clear the quarantine label to successfully launch - 🤖 **[Auto Run & Playbooks](#auto-run)** - File-system-based task runner that batch-processes markdown checklists through AI agents. Create playbooks for repeatable workflows, run in loops, and track progress with full history. Each task gets its own AI session for clean conversation context. - 🌐 **[Mobile Remote Control](#remote-access)** - Built-in web server with QR code access. Monitor and control all your agents from your phone. Supports local network access and remote tunneling via Cloudflare for access from anywhere. - 💻 **[Command Line Interface](#command-line-interface)** - Full CLI (`maestro-cli`) for headless operation. List agents/groups, run playbooks from cron jobs or CI/CD pipelines, with human-readable or JSONL output for scripting. -- 🚀 **[Multi-Instance Management](#key-concepts)** - Run unlimited Claude Code instances and terminal sessions in parallel. Each agent has its own workspace, conversation history, and isolated context. +- 🚀 **Multi-Instance Management** - Run unlimited Claude Code instances and terminal sessions in parallel. Each agent has its own workspace, conversation history, and isolated context. - 📬 **Message Queueing** - Queue messages while AI is busy; they're sent automatically when the agent becomes ready. Never lose a thought. ### Core Features -- 🔄 **[Dual-Mode Sessions](#key-concepts)** - Each agent has both an AI Terminal and Command Terminal. Switch seamlessly between AI conversation and shell commands with `Cmd+J`. +- 🔄 **Dual-Mode Sessions** - Each agent has both an AI Terminal and Command Terminal. Switch seamlessly between AI conversation and shell commands with `Cmd+J`. - ⌨️ **[Keyboard-First Design](#keyboard-shortcuts)** - Full keyboard control with customizable shortcuts. `Cmd+K` quick actions, vim-style navigation, rapid agent switching, and focus management designed for flow state. -- 📋 **[Session Discovery](#key-concepts)** - Automatically discovers and imports all Claude Code sessions, including conversations from before Maestro was installed. Browse, search, star, rename, and resume any session. -- 🔀 **[Git Integration](#key-concepts)** - Automatic repo detection, branch display, diff viewer, commit logs, and git-aware file completion. Work with git without leaving the app. +- 📋 **Session Discovery** - Automatically discovers and imports all Claude Code sessions, including conversations from before Maestro was installed. Browse, search, star, rename, and resume any session. +- 🔀 **Git Integration** - Automatic repo detection, branch display, diff viewer, commit logs, and git-aware file completion. Work with git without leaving the app. - 📁 **[File Explorer](#ui-overview)** - Browse project files with syntax highlighting, markdown preview, and image viewing. Reference files in prompts with `@` mentions. - 🔍 **[Powerful Output Filtering](#inputoutput)** - Search and filter AI output with include/exclude modes, regex support, and per-response local filters. - ⚡ **[Slash Commands](#slash-commands)** - Extensible command system with autocomplete. Create custom commands with template variables for your workflows. - 💾 **Draft Auto-Save** - Never lose work. Drafts are automatically saved and restored per session. - 🔊 **Speakable Notifications** - Audio alerts with text-to-speech announcements when agents complete tasks. -- 🎨 **[Beautiful Themes](#screenshots)** - 12 themes including Dracula, Monokai, Nord, Tokyo Night, and GitHub Light. +- 🎨 **[Beautiful Themes](THEMES.md)** - 12 themes including Dracula, Monokai, Nord, Tokyo Night, GitHub Light, and more. - 💰 **Cost Tracking** - Real-time token usage and cost tracking per session and globally. - 🏆 **[Achievements](#achievements)** - Level up from Apprentice to Titan of the Baton based on cumulative Auto Run time. 11 conductor-themed ranks to unlock. @@ -61,8 +61,8 @@ Maestro enables a **specification-first approach** to AI-assisted development. I ``` ┌─────────────────────────────────────────────────────────────────────┐ -│ 1. PLAN 2. SPECIFY 3. EXECUTE 4. REFINE │ -│ ───────── ────────── ───────── ───────── │ +│ 1. PLAN 2. SPECIFY 3. EXECUTE 4. REFINE │ +│ ───────── ────────── ───────── ───────── │ │ Discuss the Create markdown Auto Run works Review │ │ feature with docs with task through tasks, results, │ │ the AI agent checklists in fresh session update specs │ @@ -155,14 +155,13 @@ Each session shows a color-coded status indicator: ### Web Interface / Remote Control #### Chat -IMG_0163 +IMG_0163 #### Groups / Sessions -IMG_0162 +IMG_0162 #### History -IMG_0164 - +IMG_0164 ## Keyboard Shortcuts diff --git a/src/renderer/components/HistoryPanel.tsx b/src/renderer/components/HistoryPanel.tsx index dec86634..1cde1098 100644 --- a/src/renderer/components/HistoryPanel.tsx +++ b/src/renderer/components/HistoryPanel.tsx @@ -420,6 +420,9 @@ const MAX_HISTORY_IN_MEMORY = 500; // Maximum entries to keep in memory const INITIAL_DISPLAY_COUNT = 50; // Initial entries to render const LOAD_MORE_COUNT = 50; // Entries to add when scrolling +// Module-level storage for scroll positions (persists across session switches) +const scrollPositionCache = new Map(); + export const HistoryPanel = React.memo(forwardRef(function HistoryPanel({ session, theme, onJumpToClaudeSession, onResumeSession, onOpenSessionAsTab }, ref) { const [historyEntries, setHistoryEntries] = useState([]); const [activeFilters, setActiveFilters] = useState>(new Set(['AUTO', 'USER'])); @@ -436,26 +439,47 @@ export const HistoryPanel = React.memo(forwardRef(null); const itemRefs = useRef>({}); const searchInputRef = useRef(null); - const savedScrollPosition = useRef(0); + const hasRestoredScroll = useRef(false); // Load history entries function - reusable for initial load and refresh - const loadHistory = useCallback(async () => { - setIsLoading(true); + // When isRefresh=true, preserve scroll position and displayCount + const loadHistory = useCallback(async (isRefresh = false) => { + // Save current scroll position before loading + const currentScrollTop = listRef.current?.scrollTop ?? 0; + const currentDisplayCount = displayCount; + + if (!isRefresh) { + setIsLoading(true); + } + try { // Pass sessionId to filter: only show entries from this session or legacy entries without sessionId const entries = await window.maestro.history.getAll(session.cwd, session.id); // Ensure entries is an array, limit to MAX_HISTORY_IN_MEMORY const validEntries = Array.isArray(entries) ? entries : []; setHistoryEntries(validEntries.slice(0, MAX_HISTORY_IN_MEMORY)); - // Reset display count when reloading - setDisplayCount(INITIAL_DISPLAY_COUNT); + + if (isRefresh) { + // On refresh, preserve displayCount and restore scroll position + // Use RAF to ensure DOM has updated before restoring scroll + requestAnimationFrame(() => { + if (listRef.current) { + listRef.current.scrollTop = currentScrollTop; + } + }); + } else { + // Only reset display count on initial load, not refresh + setDisplayCount(INITIAL_DISPLAY_COUNT); + } } catch (error) { console.error('Failed to load history:', error); setHistoryEntries([]); } finally { - setIsLoading(false); + if (!isRefresh) { + setIsLoading(false); + } } - }, [session.cwd, session.id]); + }, [session.cwd, session.id, displayCount]); // Expose focus and refreshHistory methods to parent useImperativeHandle(ref, () => ({ @@ -467,7 +491,9 @@ export const HistoryPanel = React.memo(forwardRef { - loadHistory(); + // Pass true to indicate this is a refresh, not initial load + // This preserves scroll position and displayCount + loadHistory(true); } }), [selectedIndex, historyEntries.length, loadHistory]); @@ -577,8 +603,8 @@ export const HistoryPanel = React.memo(forwardRef { - if (listRef.current && savedScrollPosition.current > 0 && !isLoading) { - listRef.current.scrollTop = savedScrollPosition.current; + if (listRef.current && !isLoading && !hasRestoredScroll.current) { + const savedPosition = scrollPositionCache.get(session.id); + if (savedPosition !== undefined && savedPosition > 0) { + // Use requestAnimationFrame to ensure DOM has rendered + requestAnimationFrame(() => { + if (listRef.current) { + listRef.current.scrollTop = savedPosition; + } + }); + } + hasRestoredScroll.current = true; } - }, [isLoading]); + }, [isLoading, session.id]); + + // Reset the restore flag when session changes so we restore for the new session + useEffect(() => { + hasRestoredScroll.current = false; + }, [session.id]); // Reset selected index, display count, and graph reference time when filters change useEffect(() => {