feat(sessions): show a pending-question indicator on session rows
Adds a per-session pending-question badge to sidebar rows, driven by the live directory-store question state through a dedicated per-session subscription channel so unrelated streaming never re-renders rows. Collapsed parent rows roll up pending questions of hidden descendants from their owning directory stores without bootstrapping them. Question state is cloned on session delete/archive so badges clear when sessions disappear. Adds the questionChangeCallbacks sync performance counter, i18n keys for all locales, and unit tests for the subscription channel and scope selection. Fixes #2634
This commit is contained in:
@@ -22,11 +22,11 @@ import { isSessionPinned, type SessionPinnedTarget } from '@/stores/useSessionPi
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import { buildExportFilename, downloadAsMarkdown, formatSessionAsMarkdown, getExportRevealLabelKey, revealExportedMarkdown, saveAsMarkdownDesktop } from '@/lib/exportSession';
|
||||
import type { ChildSessionExport } from '@/lib/exportSession';
|
||||
import { buildSessionMessageRecordsSnapshot, useDirectoryStore, useGlobalSessionStatus, useSessionPermissions } from '@/sync/sync-context';
|
||||
import { buildSessionMessageRecordsSnapshot, useDirectoryStore, useGlobalSessionStatus, useSessionPermissions, useSessionQuestionCount } from '@/sync/sync-context';
|
||||
import { useSync } from '@/sync/use-sync';
|
||||
import { useViewportStore, viewportSessionKey } from '@/sync/viewport-store';
|
||||
import { DraggableSessionRow } from './sessionFolderDnd';
|
||||
import { nodeContainsSessionId, nodeHasPinnedMembershipChange } from './sessionNodeItemUtils';
|
||||
import { nodeContainsSessionId, nodeHasPinnedMembershipChange, selectQuestionBadgeSessionScopes } from './sessionNodeItemUtils';
|
||||
import type { SessionNodeChildRenderExtras, SessionNodeRenderExtras } from './sessionNodeItemUtils';
|
||||
import type { SessionNode } from './types';
|
||||
import { formatProjectLabel, formatSessionCompactDateLabel, formatSessionDateLabel, normalizePath, renderHighlightedText } from './utils';
|
||||
@@ -470,6 +470,11 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
// expand the other. Matches the format of menuInstanceKey.
|
||||
const expansionKey = menuInstanceKey;
|
||||
const isExpanded = hasSessionSearchQuery ? true : expandedParents.has(expansionKey);
|
||||
const questionBadgeSessionScopes = React.useMemo(
|
||||
() => selectQuestionBadgeSessionScopes(node, isExpanded, sessionDirectory),
|
||||
[isExpanded, node, sessionDirectory],
|
||||
);
|
||||
const pendingQuestionCount = useSessionQuestionCount(questionBadgeSessionScopes);
|
||||
const isSubtaskSession = Boolean((resolvedSession as Session & { parentID?: string | null }).parentID);
|
||||
const unseenCount = useSessionUnseenCount(session.id);
|
||||
const needsAttention = unseenCount > 0 && (!isSubtaskSession || notifyOnSubtasks);
|
||||
@@ -676,6 +681,9 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
}
|
||||
|
||||
const pendingPermissionCount = sessionPermissions.length;
|
||||
const pendingQuestionLabel = pendingQuestionCount === 1
|
||||
? t('sessions.sidebar.session.status.questionPendingSingle')
|
||||
: t('sessions.sidebar.session.status.questionPendingMany', { count: pendingQuestionCount });
|
||||
const showUnreadStatus = !isMovingToWorktree && !isStreaming && needsAttention && !isActive;
|
||||
const showStatusMarker = isStreaming || showUnreadStatus;
|
||||
// Both states are the same static dot; only the color separates "running"
|
||||
@@ -1292,6 +1300,12 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
<span className="leading-none">{pendingPermissionCount}</span>
|
||||
</span>
|
||||
) : null}
|
||||
{pendingQuestionCount > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 rounded bg-status-info/10 px-1 py-0.5 text-[0.7rem] text-status-info flex-shrink-0" title={pendingQuestionLabel} aria-label={pendingQuestionLabel}>
|
||||
<Icon name="question" className="h-3 w-3" />
|
||||
<span className="leading-none">{pendingQuestionCount}</span>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
|
||||
Reference in New Issue
Block a user