From e0d3b6eb7fdd63a9d910ca73b89503497f18dde5 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Thu, 27 Nov 2025 00:26:14 -0600 Subject: [PATCH] MAESTRO: Add star/bookmark functionality to Claude Sessions Browser - Added Star icon import and starredSessions state - Load starred sessions from settings on component mount - Added toggleStar function to toggle and persist star status - Modified filteredSessions to sort starred sessions to the top - Added star button UI to each session in the list - Starred sessions are persisted per-project in settings This matches the functionality already in AgentSessionsModal.tsx --- .../components/AgentSessionsBrowser.tsx | 70 +++++++++++++++++-- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/renderer/components/AgentSessionsBrowser.tsx b/src/renderer/components/AgentSessionsBrowser.tsx index a9c13800..65fa7806 100644 --- a/src/renderer/components/AgentSessionsBrowser.tsx +++ b/src/renderer/components/AgentSessionsBrowser.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; -import { Search, Clock, MessageSquare, HardDrive, Play, ChevronLeft, Loader2, Plus, X, List, Database, BarChart3, ChevronDown, User, Bot, DollarSign } from 'lucide-react'; +import { Search, Clock, MessageSquare, HardDrive, Play, ChevronLeft, Loader2, Plus, X, List, Database, BarChart3, ChevronDown, User, Bot, DollarSign, Star } from 'lucide-react'; import type { Theme, Session, LogEntry } from '../types'; import { useLayerStack } from '../contexts/LayerStackContext'; import { MODAL_PRIORITIES } from '../constants/modalPriorities'; @@ -65,6 +65,7 @@ export function AgentSessionsBrowser({ const [hasMoreMessages, setHasMoreMessages] = useState(false); const [totalMessages, setTotalMessages] = useState(0); const [messagesOffset, setMessagesOffset] = useState(0); + const [starredSessions, setStarredSessions] = useState>(new Set()); const inputRef = useRef(null); const selectedItemRef = useRef(null); @@ -178,6 +179,13 @@ export function AgentSessionsBrowser({ } try { + // Load starred sessions for this project + const starredKey = `starredClaudeSessions:${activeSession.cwd}`; + const savedStarred = await window.maestro.settings.get(starredKey); + if (savedStarred && Array.isArray(savedStarred)) { + setStarredSessions(new Set(savedStarred)); + } + const result = await window.maestro.claude.listSessions(activeSession.cwd); setSessions(result); } catch (error) { @@ -190,6 +198,25 @@ export function AgentSessionsBrowser({ loadSessions(); }, [activeSession?.cwd]); + // Toggle star status for a session + const toggleStar = useCallback(async (sessionId: string, e: React.MouseEvent) => { + e.stopPropagation(); // Don't trigger session view + + const newStarred = new Set(starredSessions); + if (newStarred.has(sessionId)) { + newStarred.delete(sessionId); + } else { + newStarred.add(sessionId); + } + setStarredSessions(newStarred); + + // Persist to settings + if (activeSession?.cwd) { + const starredKey = `starredClaudeSessions:${activeSession.cwd}`; + await window.maestro.settings.set(starredKey, Array.from(newStarred)); + } + }, [starredSessions, activeSession?.cwd]); + // Auto-view session when activeClaudeSessionId is provided (e.g., from history panel click) useEffect(() => { // Only auto-jump once per activeClaudeSessionId @@ -319,28 +346,42 @@ export function AgentSessionsBrowser({ // First filter by showAllSessions const visibleSessions = sessions.filter(isSessionVisible); + // Sort starred sessions to the top, then by modified date + const sortWithStarred = (sessionList: ClaudeSession[]) => { + return [...sessionList].sort((a, b) => { + const aStarred = starredSessions.has(a.sessionId); + const bStarred = starredSessions.has(b.sessionId); + if (aStarred && !bStarred) return -1; + if (!aStarred && bStarred) return 1; + // Within same starred status, sort by most recent + return new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime(); + }); + }; + if (!search.trim()) { - return visibleSessions; + return sortWithStarred(visibleSessions); } // For title search, filter locally (fast) if (searchMode === 'title') { const searchLower = search.toLowerCase(); - return visibleSessions.filter(s => + const filtered = visibleSessions.filter(s => s.firstMessage.toLowerCase().includes(searchLower) || s.sessionId.toLowerCase().includes(searchLower) ); + return sortWithStarred(filtered); } // For content searches, use backend results to filter sessions if (searchResults.length > 0) { const matchingIds = new Set(searchResults.map(r => r.sessionId)); - return visibleSessions.filter(s => matchingIds.has(s.sessionId)); + const filtered = visibleSessions.filter(s => matchingIds.has(s.sessionId)); + return sortWithStarred(filtered); } // If searching but no results yet, return empty (or all if still loading) - return isSearching ? visibleSessions : []; - }, [sessions, search, searchMode, searchResults, isSearching, isSessionVisible]); + return isSearching ? sortWithStarred(visibleSessions) : []; + }, [sessions, search, searchMode, searchResults, isSearching, isSessionVisible, starredSessions]); // Get search result info for a session (for display purposes) const getSearchResultInfo = useCallback((sessionId: string): SearchResult | undefined => { @@ -716,17 +757,32 @@ export function AgentSessionsBrowser({
{filteredSessions.map((session, i) => { const searchResultInfo = getSearchResultInfo(session.sessionId); + const isStarred = starredSessions.has(session.sessionId); return (
{/* Line 1: Title/first message */}