import type { TFunction } from 'i18next'; import type { Project } from '../../types/app'; import type { AdditionalSessionsByProject, ProjectSortOrder, SessionViewModel, SessionWithProvider, } from './types'; export const readProjectSortOrder = (): ProjectSortOrder => { try { const rawSettings = localStorage.getItem('claude-settings'); if (!rawSettings) { return 'name'; } const settings = JSON.parse(rawSettings) as { projectSortOrder?: ProjectSortOrder }; return settings.projectSortOrder === 'date' ? 'date' : 'name'; } catch { return 'name'; } }; export const loadStarredProjects = (): Set => { try { const saved = localStorage.getItem('starredProjects'); return saved ? new Set(JSON.parse(saved)) : new Set(); } catch { return new Set(); } }; export const persistStarredProjects = (starredProjects: Set) => { try { localStorage.setItem('starredProjects', JSON.stringify([...starredProjects])); } catch { // Keep UI responsive even if storage fails. } }; export const getSessionDate = (session: SessionWithProvider): Date => { if (session.__provider === 'cursor') { return new Date(session.createdAt || 0); } if (session.__provider === 'codex') { return new Date(session.createdAt || session.lastActivity || 0); } return new Date(session.lastActivity || 0); }; export const getSessionName = (session: SessionWithProvider, t: TFunction): string => { if (session.__provider === 'cursor') { return session.name || t('projects.untitledSession'); } if (session.__provider === 'codex') { return session.summary || session.name || t('projects.codexSession'); } return session.summary || t('projects.newSession'); }; export const getSessionTime = (session: SessionWithProvider): string => { if (session.__provider === 'cursor') { return String(session.createdAt || ''); } if (session.__provider === 'codex') { return String(session.createdAt || session.lastActivity || ''); } return String(session.lastActivity || ''); }; export const createSessionViewModel = ( session: SessionWithProvider, currentTime: Date, t: TFunction, ): SessionViewModel => { const sessionDate = getSessionDate(session); const diffInMinutes = Math.floor((currentTime.getTime() - sessionDate.getTime()) / (1000 * 60)); return { isCursorSession: session.__provider === 'cursor', isCodexSession: session.__provider === 'codex', isActive: diffInMinutes < 10, sessionName: getSessionName(session, t), sessionTime: getSessionTime(session), messageCount: Number(session.messageCount || 0), }; }; export const getAllSessions = ( project: Project, additionalSessions: AdditionalSessionsByProject, ): SessionWithProvider[] => { const claudeSessions = [ ...(project.sessions || []), ...(additionalSessions[project.name] || []), ].map((session) => ({ ...session, __provider: 'claude' as const })); const cursorSessions = (project.cursorSessions || []).map((session) => ({ ...session, __provider: 'cursor' as const, })); const codexSessions = (project.codexSessions || []).map((session) => ({ ...session, __provider: 'codex' as const, })); return [...claudeSessions, ...cursorSessions, ...codexSessions].sort( (a, b) => getSessionDate(b).getTime() - getSessionDate(a).getTime(), ); }; export const getProjectLastActivity = ( project: Project, additionalSessions: AdditionalSessionsByProject, ): Date => { const sessions = getAllSessions(project, additionalSessions); if (sessions.length === 0) { return new Date(0); } return sessions.reduce((latest, session) => { const sessionDate = getSessionDate(session); return sessionDate > latest ? sessionDate : latest; }, new Date(0)); }; export const sortProjects = ( projects: Project[], projectSortOrder: ProjectSortOrder, starredProjects: Set, additionalSessions: AdditionalSessionsByProject, ): Project[] => { const byName = [...projects]; byName.sort((projectA, projectB) => { const aStarred = starredProjects.has(projectA.name); const bStarred = starredProjects.has(projectB.name); if (aStarred && !bStarred) { return -1; } if (!aStarred && bStarred) { return 1; } if (projectSortOrder === 'date') { return ( getProjectLastActivity(projectB, additionalSessions).getTime() - getProjectLastActivity(projectA, additionalSessions).getTime() ); } return (projectA.displayName || projectA.name).localeCompare(projectB.displayName || projectB.name); }); return byName; }; export const filterProjects = (projects: Project[], searchFilter: string): Project[] => { const normalizedSearch = searchFilter.trim().toLowerCase(); if (!normalizedSearch) { return projects; } return projects.filter((project) => { const displayName = (project.displayName || project.name).toLowerCase(); const projectName = project.name.toLowerCase(); return displayName.includes(normalizedSearch) || projectName.includes(normalizedSearch); }); }; export const getTaskIndicatorStatus = ( project: Project, mcpServerStatus: { hasMCPServer?: boolean; isConfigured?: boolean } | null, ) => { const projectConfigured = Boolean(project.taskmaster?.hasTaskmaster); const mcpConfigured = Boolean(mcpServerStatus?.hasMCPServer && mcpServerStatus?.isConfigured); if (projectConfigured && mcpConfigured) { return 'fully-configured'; } if (projectConfigured) { return 'taskmaster-only'; } if (mcpConfigured) { return 'mcp-only'; } return 'not-configured'; };