From 7eb0ac1478bf568324f5620b5c26d81962ca0947 Mon Sep 17 00:00:00 2001 From: Rafael Audibert <32079912+rafaeelaudibert@users.noreply.github.com> Date: Thu, 28 May 2026 17:59:35 -0300 Subject: [PATCH 1/5] Reconcile inbox sidebar badge with toolbar headline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The badge next to "Inbox" in the sidebar counts reports the current user needs to action right now (assigned to them, ready, immediately actionable). The "Reports (N)" headline inside the inbox view was the server's raw total for the active filter set — a wider, unrelated number — and the two could disagree by a lot. - Extract a shared `useUpForReviewCount` hook so both surfaces resolve the count from the same React Query cache entry. - Rename the toolbar headline to "Up for review (N)" so its semantics match the badge by construction. The previous "X up for review · Y in research pipeline" sub-line went away with it (the headline now carries that meaning). - Move the raw inbox total into a clearer sub-line that says "N reports in inbox · M still processing", with the search-active variant calling it out as "X of Y match search". - Add explanatory tooltips on both the headline and the sub-line so the distinction is discoverable. Generated-By: PostHog Code Task-Id: cd26fe55-9707-4905-9394-e55db22f511c --- .../inbox/components/InboxSignalsTab.tsx | 1 - .../inbox/components/list/SignalsToolbar.tsx | 70 ++++++++++++------- .../features/inbox/hooks/useInboxReports.ts | 24 +++++++ .../sidebar/components/SidebarMenu.tsx | 23 ++---- 4 files changed, 77 insertions(+), 41 deletions(-) diff --git a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx index 955db61c9d..b0e2633d39 100644 --- a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx +++ b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx @@ -757,7 +757,6 @@ export function InboxSignalsTab() { isSearchActive={!!searchQuery.trim()} livePolling={inboxPollingActive} isFetching={isFetching} - readyCount={readyCount} processingCount={processingCount} pipelinePausedUntil={signalProcessingState?.paused_until} reports={reports} diff --git a/apps/code/src/renderer/features/inbox/components/list/SignalsToolbar.tsx b/apps/code/src/renderer/features/inbox/components/list/SignalsToolbar.tsx index ad66c46a23..6639ade4a4 100644 --- a/apps/code/src/renderer/features/inbox/components/list/SignalsToolbar.tsx +++ b/apps/code/src/renderer/features/inbox/components/list/SignalsToolbar.tsx @@ -1,6 +1,7 @@ import { Button, type ButtonProps } from "@components/ui/Button"; import { Tooltip as ActionTooltip } from "@components/ui/Tooltip"; import { useInboxBulkActions } from "@features/inbox/hooks/useInboxBulkActions"; +import { useUpForReviewCount } from "@features/inbox/hooks/useInboxReports"; import { useInboxSignalsFilterStore } from "@features/inbox/stores/inboxSignalsFilterStore"; import { INBOX_REFETCH_INTERVAL_MS } from "@features/inbox/utils/inboxConstants"; import { @@ -42,7 +43,6 @@ interface SignalsToolbarProps { isSearchActive: boolean; livePolling?: boolean; isFetching?: boolean; - readyCount?: number; processingCount?: number; pipelinePausedUntil?: string | null; searchDisabledReason?: string | null; @@ -103,6 +103,12 @@ function formatPauseRemaining(pausedUntil: string): string { const inboxLivePollingTooltip = `Inbox is focused – syncing reports every ${Math.round(INBOX_REFETCH_INTERVAL_MS / 1000)}s…`; +const UP_FOR_REVIEW_TOOLTIP = + "Reports assigned to you that are ready and immediately actionable. This is the same number shown on the badge next to Inbox in the sidebar."; + +const INBOX_TOTAL_TOOLTIP = + "All reports currently in your inbox view, after any status, source, or reviewer filters you have applied."; + function bulkMenuItemTooltip( explanationWhenEnabled: string, headlineWhenDisabled: string, @@ -255,7 +261,6 @@ export function SignalsToolbar({ isSearchActive, livePolling = false, isFetching = false, - readyCount, processingCount = 0, pipelinePausedUntil, searchDisabledReason, @@ -268,6 +273,15 @@ export function SignalsToolbar({ isDismissMutationPending = false, onReportAction, }: SignalsToolbarProps) { + // Shared with the sidebar badge: both call this hook with matching params + // and React Query dedupes the underlying fetch, so the two numbers cannot + // disagree even when the user has applied filters that hide some reports + // from the list below. + const upForReviewCount = useUpForReviewCount({ + refetchInterval: livePolling ? INBOX_REFETCH_INTERVAL_MS : false, + refetchIntervalInBackground: false, + staleTime: livePolling ? INBOX_REFETCH_INTERVAL_MS : 15_000, + }); const searchQuery = useInboxSignalsFilterStore((s) => s.searchQuery); const setSearchQuery = useInboxSignalsFilterStore((s) => s.setSearchQuery); const [showSnoozeConfirm, setShowSnoozeConfirm] = useState(false); @@ -291,21 +305,27 @@ export function SignalsToolbar({ reingestSelected, } = useInboxBulkActions(reports, effectiveBulkIds); - const countLabel = isSearchActive - ? `${filteredCount} of ${totalCount}` - : `${totalCount}`; - - const pipelineHintParts = [ - readyCount != null && processingCount > 0 - ? `${readyCount} up for review • ${processingCount} in research pipeline` - : null, - pipelinePausedUntil - ? `Pipeline paused · resumes in ${formatPauseRemaining(pipelinePausedUntil)}` - : null, - ].filter(Boolean); - - const pipelineHint = - pipelineHintParts.length > 0 ? pipelineHintParts.join(" · ") : null; + // Sub-line: how many reports the user is browsing right now (search-filtered + // count when searching, raw inbox total otherwise) plus pipeline status. The + // headline above is the narrower "needs your action" count; this line is the + // wider "everything in your inbox" context. + const inboxSubParts: string[] = []; + if (isSearchActive) { + inboxSubParts.push(`${filteredCount} of ${totalCount} match search`); + } else { + inboxSubParts.push( + `${totalCount} ${totalCount === 1 ? "report" : "reports"} in inbox`, + ); + if (processingCount > 0) { + inboxSubParts.push(`${processingCount} still processing`); + } + } + if (pipelinePausedUntil) { + inboxSubParts.push( + `Pipeline paused · resumes in ${formatPauseRemaining(pipelinePausedUntil)}`, + ); + } + const inboxSubLine = inboxSubParts.join(" · "); const multiSelectBulkActions = selectedCount > 1; @@ -469,9 +489,11 @@ export function SignalsToolbar({ - - Reports ({countLabel}) - + + + Up for review ({upForReviewCount}) + + {livePolling ? ( ) : null} - {pipelineHint && !isSearchActive ? ( - - {pipelineHint} + + + {inboxSubLine} - ) : null} + {onConfigureSources ? (