fix: address all PR review findings for bulk delete/archive functionality
Comprehensive code quality improvements addressing all review comments: **Critical Fixes:** - Fixed double-toggle bug where checkbox called onToggleSelect twice - Added accessibility attributes (role, tabIndex, onKeyDown) to interactive div **Error Handling:** - Removed error re-throws in async event handlers to prevent unhandled rejections - Added error logging to empty catch blocks in session-storage.ts - Added error handling with .catch() for dropdown menu promise rejections - Added logging for partial failures in bulk operations **Code Quality:** - Replaced non-null assertion (!) with explicit null check in session-manager.ts - Removed redundant async/await wrappers in archive/unarchive handlers - Removed duplicate loadInsightsSession calls from store functions - Added skip-first-run guard to prevent double load on component mount - Added clarifying comment for showArchived useEffect dependency **Performance:** - Eliminated redundant IPC calls by removing duplicate session reloads - Fixed flicker issue caused by double reload with different filters All changes maintain backward compatibility and improve user experience. Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9f3ac54844
commit
02b40fa342
@@ -20,8 +20,9 @@ export class SessionManager {
|
||||
*/
|
||||
loadSession(projectId: string, projectPath: string): InsightsSession | null {
|
||||
// Check in-memory cache first
|
||||
if (this.sessions.has(projectId)) {
|
||||
return this.sessions.get(projectId)!;
|
||||
const cachedSession = this.sessions.get(projectId);
|
||||
if (cachedSession) {
|
||||
return cachedSession;
|
||||
}
|
||||
|
||||
// Migrate old format if needed
|
||||
|
||||
@@ -75,7 +75,8 @@ export class SessionStorage {
|
||||
session.archivedAt = new Date();
|
||||
this.saveSession(projectPath, session);
|
||||
return true;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(`[SessionStorage] Failed to archive session ${sessionId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -91,7 +92,8 @@ export class SessionStorage {
|
||||
delete session.archivedAt;
|
||||
this.saveSession(projectPath, session);
|
||||
return true;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(`[SessionStorage] Failed to unarchive session ${sessionId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ export function ChatHistorySidebar({
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Clear selection when showArchived toggles
|
||||
// Clear selection when showArchived toggles - prevents selecting archived sessions when filter changes
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: showArchived is intentionally a dependency to reset selection on filter change
|
||||
useEffect(() => {
|
||||
setSelectedIds(new Set());
|
||||
}, [showArchived]);
|
||||
@@ -144,8 +145,6 @@ export function ChatHistorySidebar({
|
||||
setBulkDeleteOpen(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete sessions:', error);
|
||||
// Re-throw to allow parent components to handle
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -157,8 +156,6 @@ export function ChatHistorySidebar({
|
||||
setSelectedIds(new Set());
|
||||
} catch (error) {
|
||||
console.error('Failed to archive sessions:', error);
|
||||
// Re-throw to allow parent components to handle
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -304,8 +301,8 @@ export function ChatHistorySidebar({
|
||||
onCancelEdit={handleCancelEdit}
|
||||
onEditTitleChange={setEditTitle}
|
||||
onDelete={() => setDeleteSessionId(session.id)}
|
||||
onArchive={onArchiveSession ? async () => await onArchiveSession(session.id) : undefined}
|
||||
onUnarchive={onUnarchiveSession ? async () => await onUnarchiveSession(session.id) : undefined}
|
||||
onArchive={onArchiveSession ? () => onArchiveSession(session.id).catch((e) => console.error('Archive failed:', e)) : undefined}
|
||||
onUnarchive={onUnarchiveSession ? () => onUnarchiveSession(session.id).catch((e) => console.error('Unarchive failed:', e)) : undefined}
|
||||
isArchived={!!session.archivedAt}
|
||||
isSelectionMode={isSelectionMode}
|
||||
isSelected={selectedIds.has(session.id)}
|
||||
@@ -476,23 +473,25 @@ function SessionItem({
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={cn(
|
||||
'group relative cursor-pointer px-2 py-2 transition-colors hover:bg-muted',
|
||||
isActive && 'bg-primary/10 hover:bg-primary/15',
|
||||
isArchived && 'opacity-50'
|
||||
)}
|
||||
onClick={isSelectionMode ? undefined : onSelect}
|
||||
onKeyDown={(e) => {
|
||||
if (!isSelectionMode && (e.key === 'Enter' || e.key === ' ')) {
|
||||
e.preventDefault();
|
||||
onSelect();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Content with reserved space for the menu button */}
|
||||
<div className="flex items-center gap-1.5 pr-7">
|
||||
{isSelectionMode ? (
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleSelect();
|
||||
}}
|
||||
className="shrink-0"
|
||||
>
|
||||
<div className="shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => onToggleSelect()}
|
||||
|
||||
@@ -149,8 +149,13 @@ export function Insights({ projectId }: InsightsProps) {
|
||||
return cleanup;
|
||||
}, [projectId]);
|
||||
|
||||
// Reload sessions when showArchived changes
|
||||
// Reload sessions when showArchived changes (skip first run to avoid duplicate load)
|
||||
const isFirstRun = useRef(true);
|
||||
useEffect(() => {
|
||||
if (isFirstRun.current) {
|
||||
isFirstRun.current = false;
|
||||
return;
|
||||
}
|
||||
loadInsightsSessions(projectId, showArchived);
|
||||
}, [projectId, showArchived]);
|
||||
|
||||
@@ -221,13 +226,23 @@ export function Insights({ projectId }: InsightsProps) {
|
||||
};
|
||||
|
||||
const handleDeleteSessions = async (sessionIds: string[]) => {
|
||||
await deleteSessions(projectId, sessionIds);
|
||||
const result = await deleteSessions(projectId, sessionIds);
|
||||
await loadInsightsSessions(projectId, showArchived);
|
||||
|
||||
// Log partial failures for debugging
|
||||
if (result.failedIds && result.failedIds.length > 0) {
|
||||
console.warn(`Failed to delete ${result.failedIds.length} session(s):`, result.failedIds);
|
||||
}
|
||||
};
|
||||
|
||||
const handleArchiveSessions = async (sessionIds: string[]) => {
|
||||
await archiveSessions(projectId, sessionIds);
|
||||
const result = await archiveSessions(projectId, sessionIds);
|
||||
await loadInsightsSessions(projectId, showArchived);
|
||||
|
||||
// Log partial failures for debugging
|
||||
if (result.failedIds && result.failedIds.length > 0) {
|
||||
console.warn(`Failed to archive ${result.failedIds.length} session(s):`, result.failedIds);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleShowArchived = () => {
|
||||
|
||||
@@ -316,7 +316,6 @@ export async function renameSession(projectId: string, sessionId: string, newTit
|
||||
export async function deleteSessions(projectId: string, sessionIds: string[]): Promise<{ success: boolean; failedIds?: string[] }> {
|
||||
const result = await window.electronAPI.deleteInsightsSessions(projectId, sessionIds);
|
||||
if (result.success) {
|
||||
await loadInsightsSession(projectId);
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false, failedIds: result.data?.failedIds };
|
||||
@@ -324,17 +323,12 @@ export async function deleteSessions(projectId: string, sessionIds: string[]): P
|
||||
|
||||
export async function archiveSession(projectId: string, sessionId: string): Promise<boolean> {
|
||||
const result = await window.electronAPI.archiveInsightsSession(projectId, sessionId);
|
||||
if (result.success) {
|
||||
await loadInsightsSession(projectId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return result.success;
|
||||
}
|
||||
|
||||
export async function archiveSessions(projectId: string, sessionIds: string[]): Promise<{ success: boolean; failedIds?: string[] }> {
|
||||
const result = await window.electronAPI.archiveInsightsSessions(projectId, sessionIds);
|
||||
if (result.success) {
|
||||
await loadInsightsSession(projectId);
|
||||
return { success: true };
|
||||
}
|
||||
return { success: false, failedIds: result.data?.failedIds };
|
||||
@@ -342,11 +336,7 @@ export async function archiveSessions(projectId: string, sessionIds: string[]):
|
||||
|
||||
export async function unarchiveSession(projectId: string, sessionId: string): Promise<boolean> {
|
||||
const result = await window.electronAPI.unarchiveInsightsSession(projectId, sessionId);
|
||||
if (result.success) {
|
||||
await loadInsightsSession(projectId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return result.success;
|
||||
}
|
||||
|
||||
export async function updateModelConfig(projectId: string, sessionId: string, modelConfig: InsightsModelConfig): Promise<boolean> {
|
||||
|
||||
Reference in New Issue
Block a user