fix: normalize project session payloads

The sidebar had to understand cursorSessions, codexSessions,
and other provider buckets because /api/projects exposed
provider-shaped arrays.

That leaked backend adapter storage into project state and made
frontend behavior drift each time a provider needed another bucket
or exception.

Return one sessions list with provider metadata instead. Project
state, search, and running-session filtering now share one contract,
while provider-specific storage remains behind the backend boundary.
This commit is contained in:
Haileyesus
2026-06-15 13:43:18 +03:00
parent 2abb45636b
commit d0adddbbda
9 changed files with 72 additions and 234 deletions

View File

@@ -594,16 +594,7 @@ export function useSidebarController({
return sortedProjects.reduce<Project[]>((acc, project) => {
const sessions = (project.sessions ?? []).filter((session) => activeSessionIds.has(String(session.id)));
const cursorSessions = (project.cursorSessions ?? []).filter((session) => activeSessionIds.has(String(session.id)));
const codexSessions = (project.codexSessions ?? []).filter((session) => activeSessionIds.has(String(session.id)));
const geminiSessions = (project.geminiSessions ?? []).filter((session) => activeSessionIds.has(String(session.id)));
const opencodeSessions = (project.opencodeSessions ?? []).filter((session) => activeSessionIds.has(String(session.id)));
const runningCount =
sessions.length
+ cursorSessions.length
+ codexSessions.length
+ geminiSessions.length
+ opencodeSessions.length;
const runningCount = sessions.length;
if (runningCount === 0) {
return acc;
@@ -612,10 +603,6 @@ export function useSidebarController({
acc.push({
...project,
sessions,
cursorSessions,
codexSessions,
geminiSessions,
opencodeSessions,
sessionMeta: {
...project.sessionMeta,
total: runningCount,

View File

@@ -61,10 +61,6 @@ export type SidebarProps = {
};
export type SessionViewModel = {
isCursorSession: boolean;
isCodexSession: boolean;
isGeminiSession: boolean;
isOpenCodeSession: boolean;
isActive: boolean;
sessionName: string;
sessionTime: string;

View File

@@ -1,6 +1,6 @@
import type { TFunction } from 'i18next';
import type { Project } from '../../../types/app';
import type { LLMProvider, Project, ProjectSession } from '../../../types/app';
import type { ProjectSortOrder, SettingsProject, SessionViewModel, SessionWithProvider } from '../types/types';
export const readProjectSortOrder = (): ProjectSortOrder => {
@@ -61,6 +61,13 @@ const getUpdatedTimestamp = (session: SessionWithProvider): string => {
return String(session.lastActivity || '');
};
const getSessionProvider = (session: ProjectSession): LLMProvider => {
const provider = session.__provider ?? session.provider;
return typeof provider === 'string' && provider.trim()
? provider as LLMProvider
: 'claude';
};
export const getSessionDate = (session: SessionWithProvider): Date => {
return new Date(getUpdatedTimestamp(session) || getCreatedTimestamp(session) || 0);
};
@@ -82,10 +89,6 @@ export const createSessionViewModel = (
const diffInMinutes = Math.floor((currentTime.getTime() - sessionDate.getTime()) / (1000 * 60));
return {
isCursorSession: session.__provider === 'cursor',
isCodexSession: session.__provider === 'codex',
isGeminiSession: session.__provider === 'gemini',
isOpenCodeSession: session.__provider === 'opencode',
isActive: diffInMinutes < 10,
sessionName: getSessionName(session, t),
sessionTime: getSessionTime(session),
@@ -94,32 +97,10 @@ export const createSessionViewModel = (
};
export const getAllSessions = (project: Project): SessionWithProvider[] => {
const claudeSessions = [...(project.sessions || [])].map((session) => ({
return (project.sessions || []).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,
}));
const geminiSessions = (project.geminiSessions || []).map((session) => ({
...session,
__provider: 'gemini' as const,
}));
const opencodeSessions = (project.opencodeSessions || []).map((session) => ({
...session,
__provider: 'opencode' as const,
}));
return [...claudeSessions, ...cursorSessions, ...codexSessions, ...geminiSessions, ...opencodeSessions].sort(
__provider: getSessionProvider(session),
})).sort(
(a, b) => getSessionDate(b).getTime() - getSessionDate(a).getTime(),
);
};

View File

@@ -179,7 +179,7 @@ export default function SidebarSessionItem({
</div>
</div>
{!isProcessing && !sessionView.isCursorSession && (
{!isProcessing && (
<button
className="ml-1 flex h-5 w-5 items-center justify-center rounded-md bg-red-50 opacity-70 transition-transform active:scale-95 dark:bg-red-900/20"
onClick={(event) => {
@@ -309,7 +309,7 @@ export default function SidebarSessionItem({
>
<Edit2 className="h-3 w-3 text-gray-600 dark:text-gray-400" />
</button>
{!isProcessing && !sessionView.isCursorSession && (
{!isProcessing && (
<button
className="flex h-6 w-6 items-center justify-center rounded bg-red-50 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/40"
onClick={(event) => {