diff --git a/src/renderer/components/sidebar/SessionItem.tsx b/src/renderer/components/sidebar/SessionItem.tsx
index 35098c35..b05565b2 100644
--- a/src/renderer/components/sidebar/SessionItem.tsx
+++ b/src/renderer/components/sidebar/SessionItem.tsx
@@ -8,9 +8,10 @@ import { useCallback, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useStore } from '@renderer/store';
+import { formatSessionLabel, parseSessionTitle } from '@renderer/utils/sessionTitleParser';
import { formatTokensCompact } from '@shared/utils/tokenFormatting';
import { formatDistanceToNowStrict } from 'date-fns';
-import { EyeOff, MessageSquare, Pin } from 'lucide-react';
+import { EyeOff, MessageSquare, Pin, Play, RotateCw, Users } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { OngoingIndicator } from '../common/OngoingIndicator';
@@ -178,7 +179,7 @@ export const SessionItem = ({
type: 'session',
sessionId: session.id,
projectId: activeProjectId,
- label: session.firstMessage?.slice(0, 50) ?? 'Session',
+ label: formatSessionLabel(session.firstMessage),
},
forceNewTab ? { forceNewTab } : { replaceActiveTab: true }
);
@@ -191,7 +192,7 @@ export const SessionItem = ({
setContextMenu({ x: e.clientX, y: e.clientY });
}, []);
- const sessionLabel = session.firstMessage?.slice(0, 50) ?? 'Session';
+ const sessionLabel = formatSessionLabel(session.firstMessage);
const handleOpenInCurrentPane = useCallback(() => {
if (!activeProjectId) return;
@@ -253,49 +254,86 @@ export const SessionItem = ({
...(isHidden ? { opacity: 0.5 } : {}),
}}
>
- {/* First line: title + ongoing indicator + pin/hidden icons */}
-
- {multiSelectActive && (
-
onToggleSelect?.()}
- onClick={(e) => e.stopPropagation()}
- className="size-3.5 shrink-0 accent-blue-500"
- />
- )}
- {session.isOngoing &&
}
- {isPinned &&
}
- {isHidden &&
}
-
- {session.firstMessage ?? 'Untitled'}
-
-
-
- {/* Second line: message count + time + context consumption */}
-
-
-
- {session.messageCount}
-
-
·
-
{formatShortTime(new Date(session.createdAt))}
- {session.contextConsumption != null && session.contextConsumption > 0 && (
+ {(() => {
+ const parsed = parseSessionTitle(session.firstMessage);
+ const isTeam = parsed.kind !== 'regular';
+ return (
<>
-
·
-
+ {/* First line: title + ongoing indicator + pin/hidden icons */}
+
+ {multiSelectActive && (
+
onToggleSelect?.()}
+ onClick={(e) => e.stopPropagation()}
+ className="size-3.5 shrink-0 accent-blue-500"
+ />
+ )}
+ {session.isOngoing &&
}
+ {isPinned &&
}
+ {isHidden &&
}
+ {isTeam ? (
+
+
+ {parsed.displayText}
+
+ ) : (
+
+ {parsed.displayText}
+
+ )}
+
+
+ {/* Second line: metadata */}
+
+ {isTeam && parsed.projectName && (
+ <>
+
{parsed.projectName}
+
·
+ >
+ )}
+ {isTeam && (
+ <>
+
+ {parsed.kind === 'team-resume' ? (
+
+ ) : (
+
+ )}
+ {parsed.kind === 'team-resume' ? 'resume' : 'new'}
+
+
·
+ >
+ )}
+
+
+ {session.messageCount}
+
+
·
+
{formatShortTime(new Date(session.createdAt))}
+ {session.contextConsumption != null && session.contextConsumption > 0 && (
+ <>
+
·
+
+ >
+ )}
+
>
- )}
-
+ );
+ })()}
{contextMenu &&
diff --git a/src/renderer/components/team/TeamSessionsSection.tsx b/src/renderer/components/team/TeamSessionsSection.tsx
index 13929447..7e19cb64 100644
--- a/src/renderer/components/team/TeamSessionsSection.tsx
+++ b/src/renderer/components/team/TeamSessionsSection.tsx
@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { useStore } from '@renderer/store';
import { resolveProjectIdByPath } from '@renderer/utils/projectLookup';
+import { formatSessionLabel } from '@renderer/utils/sessionTitleParser';
import { formatDistanceToNowStrict } from 'date-fns';
import {
AlertCircle,
@@ -69,7 +70,7 @@ export const TeamSessionsSection = ({
type: 'session',
sessionId: session.id,
projectId,
- label: session.firstMessage?.slice(0, 50) ?? 'Session',
+ label: formatSessionLabel(session.firstMessage),
},
{ forceNewTab: true }
);
@@ -173,7 +174,7 @@ const SessionRow = ({
onToggleFilter,
}: SessionRowProps): React.JSX.Element => {
const timeAgo = formatShortTime(new Date(session.createdAt));
- const label = session.firstMessage ?? 'Untitled session';
+ const label = formatSessionLabel(session.firstMessage);
return (
{
selectedTeamName,
selectedTeamData,
selectedTeamLoading,
+ selectedTeamError,
selectTeam,
openTeamTab,
setPendingReviewRequest,
@@ -32,6 +37,7 @@ export const GlobalTaskDetailDialog = (): React.JSX.Element | null => {
selectedTeamName: s.selectedTeamName,
selectedTeamData: s.selectedTeamData,
selectedTeamLoading: s.selectedTeamLoading,
+ selectedTeamError: s.selectedTeamError,
selectTeam: s.selectTeam,
openTeamTab: s.openTeamTab,
setPendingReviewRequest: s.setPendingReviewRequest,
@@ -41,6 +47,11 @@ export const GlobalTaskDetailDialog = (): React.JSX.Element | null => {
const teamName = globalTaskDetail?.teamName ?? '';
const taskId = globalTaskDetail?.taskId ?? '';
+ const hasTargetTeamData = hasSelectedTargetTeamData(
+ teamName,
+ selectedTeamName,
+ selectedTeamData?.teamName
+ );
// Load full team data in the background to enable "as before" details (logs/changes/members).
useEffect(() => {
@@ -65,13 +76,7 @@ export const GlobalTaskDetailDialog = (): React.JSX.Element | null => {
teamName,
]);
- const isFullTeamLoaded = selectedTeamName === teamName && !!selectedTeamData;
- // Team data is still loading when:
- // - selectTeam() hasn't updated selectedTeamName yet (team switch pending)
- // - selectedTeamName matches but IPC fetch is still in flight
- const isThisTeamLoading =
- selectedTeamName !== teamName ||
- (selectedTeamName === teamName && selectedTeamLoading && !selectedTeamData);
+ const isFullTeamLoaded = hasTargetTeamData;
const taskMap = useMemo(() => {
const map = new Map();
@@ -119,12 +124,21 @@ export const GlobalTaskDetailDialog = (): React.JSX.Element | null => {
const kanbanTaskState = isFullTeamLoaded
? selectedTeamData?.kanbanState.tasks[taskId]
: undefined;
+ const loading = shouldKeepGlobalTaskDialogLoading({
+ teamName,
+ taskId,
+ selectedTeamName,
+ selectedTeamDataPresent: hasTargetTeamData,
+ selectedTeamLoading,
+ selectedTeamError,
+ hasTaskInMap: taskMap.has(taskId),
+ });
return (
{
if (!v && lightboxOpenRef.current) return;
- if (!v) onClose();
+ if (!v) handleClose();
}}
>
{
const isLead = session.id === leadSessionId;
const isSelected = filter.sessionId === session.id;
- const label = session.firstMessage?.slice(0, 50) ?? session.id.slice(0, 8);
+ const label = formatSessionLabel(session.firstMessage) || session.id.slice(0, 8);
return (