import { useMemo } from 'react' import type { SessionSummary } from '@/types/api' import { AgentFlavorIcon } from '@/components/AgentFlavorIcon' import { ScheduleIcon } from '@/components/icons' import { HoverTooltip, SESSION_ROW_TOOLTIP_FOCUS_CLASS, useSessionRowTooltipIds } from '@/components/HoverTooltip' import { getAttentionLabel, SessionAttentionIndicator } from '@/components/SessionAttentionIndicator' import { classifySessionAttention } from '@/lib/sessionAttention' import { getSessionLastSeenAt } from '@/lib/sessionLastSeen' import { formatRelativeTime } from '@/lib/relativeTime' import { formatScheduledTooltipDetail } from '@/lib/scheduledTime' import { getCodexImportedAt } from '@/lib/codexImportedSessions' import { getSessionTitle } from '@/lib/sessionTitle' import { useTranslation } from '@/lib/use-translation' import { getWorktreeSessionLabel } from '@/lib/sessionWorktreeLabel' function LoaderIcon(props: { className?: string }) { return ( ) } function BulbIcon(props: { className?: string }) { return ( ) } const ATTENTION_DOT_CLASS = { permission: 'bg-amber-500 animate-pulse', input: 'bg-blue-500', background: 'bg-blue-400', unread: 'bg-[var(--app-link)]', } as const function getTodoProgress(session: SessionSummary): { completed: number; total: number } | null { if (!session.todoProgress) return null if (session.todoProgress.completed === session.todoProgress.total) return null return session.todoProgress } function formatCodexImportedRelativeTime( value: number, t: (key: string, params?: Record) => string ): string | null { const ms = value < 1_000_000_000_000 ? value * 1000 : value if (!Number.isFinite(ms)) return null const delta = Date.now() - ms if (delta < 60_000) return t('session.time.importedFromCodex.justNow') const minutes = Math.floor(delta / 60_000) if (minutes < 60) return t('session.time.importedFromCodex.minutesAgo', { n: minutes }) const hours = Math.floor(minutes / 60) if (hours < 24) return t('session.time.importedFromCodex.hoursAgo', { n: hours }) const days = Math.floor(hours / 24) if (days < 7) return t('session.time.importedFromCodex.daysAgo', { n: days }) return formatRelativeTime(value, t) } function getSessionTimeLabel( session: SessionSummary, t: (key: string, params?: Record) => string ): string | null { const importedAt = session.metadata?.flavor === 'codex' ? getCodexImportedAt(session.metadata?.agentSessionId) : null if (importedAt !== null) { return formatCodexImportedRelativeTime(importedAt, t) } return formatRelativeTime(session.updatedAt, t) } /** * Presentational session row — same chrome as the sidebar SessionItem body * (flavor, title, thinking/attention, schedule, todos, relative time, path). * Used by the session list and by rich-composer mention chip tooltips. */ export function SessionRowSummary(props: { session: SessionSummary showPath?: boolean showDetailedStatus?: boolean selected?: boolean /** * When false, attention is a bare colored dot (no nested HoverTooltip). * Use false inside an already-open chip tooltip portal. */ nestedTooltips?: boolean /** Pass from parent when the parent owns `aria-describedby` (session list). */ attentionTooltipId?: string scheduleTooltipId?: string className?: string /** Rows inside the pinned "in progress" section skip the text label (dot only). */ inRunningSection?: boolean /** Short project name shown under the title (pinned "in progress" rows). */ projectLabel?: string /** Machine label shown next to the project name (pinned "in progress" rows). */ machineLabel?: string }) { const { session: s, showPath = true, showDetailedStatus = true, selected = false, nestedTooltips = true, attentionTooltipId: attentionTooltipIdProp, scheduleTooltipId: scheduleTooltipIdProp, className, inRunningSection = false, projectLabel, machineLabel, } = props const { t } = useTranslation() const sessionName = getSessionTitle(s) const worktreeLabel = getWorktreeSessionLabel(s) const todoProgress = getTodoProgress(s) const attention = useMemo( () => showDetailedStatus ? classifySessionAttention(s, { selected, lastSeenAt: getSessionLastSeenAt(s.id), }) : null, [s, selected, showDetailedStatus] ) const attentionLabel = attention ? getAttentionLabel(attention, t) : null const urgentAttention = attention !== null && (attention.kind === 'permission' || attention.kind === 'input') const scheduledLabel = s.futureScheduledMessageCount > 1 ? t('session.item.scheduledMessages', { count: s.futureScheduledMessageCount }) : t('session.item.scheduledMessage') const hasScheduleTooltip = showDetailedStatus && s.futureScheduledMessageCount > 0 const ownedIds = useSessionRowTooltipIds( Boolean(attention) && nestedTooltips && !attentionTooltipIdProp, hasScheduleTooltip && nestedTooltips && !scheduleTooltipIdProp ) const attentionId = attentionTooltipIdProp ?? ownedIds.attentionId const scheduleId = scheduleTooltipIdProp ?? ownedIds.scheduleId const timeLabel = getSessionTimeLabel(s, t) return (
{sessionName}
{s.active && s.thinking ? ( ) : urgentAttention && nestedTooltips && attentionId ? ( ) : urgentAttention ? ( ) : showDetailedStatus && attention?.kind === 'background' && nestedTooltips && attentionId ? ( ) : showDetailedStatus && attention?.kind === 'background' ? ( ) : s.active && (s.backgroundTaskCount ?? 0) > 0 ? ( ) : s.active && (s.pendingRequestsCount ?? 0) > 0 ? ( ) : attention && nestedTooltips && attentionId ? ( ) : attention ? ( ) : null} {hasScheduleTooltip && nestedTooltips && scheduleId ? ( } side="bottom" align="start" className="shrink-0" revealOnParentFocusClass={SESSION_ROW_TOOLTIP_FOCUS_CLASS} > {scheduledLabel} {formatScheduledTooltipDetail(s, t)} ) : hasScheduleTooltip ? ( ) : null}
{todoProgress ? ( {todoProgress.completed}/{todoProgress.total} ) : null} {!attention && s.pendingRequestsCount > 0 ? ( {t('session.item.pending')} {s.pendingRequestsCount} ) : null} {timeLabel ? ( {timeLabel} ) : null}
{projectLabel || machineLabel ? (
{[projectLabel, machineLabel].filter(Boolean).join(' · ')}
) : showPath || worktreeLabel ? (
{worktreeLabel ?? s.metadata?.path ?? s.id}
) : null}
) }