@@ -11,6 +11,7 @@ import { useInboxReportSelectionStore } from "@features/inbox/stores/inboxReport
1111import { useInboxSignalsFilterStore } from "@features/inbox/stores/inboxSignalsFilterStore" ;
1212import { useInboxSignalsSidebarStore } from "@features/inbox/stores/inboxSignalsSidebarStore" ;
1313import { useInboxSourcesDialogStore } from "@features/inbox/stores/inboxSourcesDialogStore" ;
14+ import { getEffectiveBulkSelectionIds } from "@features/inbox/utils/bulkSelection" ;
1415import {
1516 buildSignalReportListOrdering ,
1617 buildStatusFilterParam ,
@@ -110,11 +111,26 @@ export function InboxSignalsTab() {
110111 const selectedReportIds = useInboxReportSelectionStore (
111112 ( s ) => s . selectedReportIds ?? [ ] ,
112113 ) ;
114+ const setSelectedReportIds = useInboxReportSelectionStore (
115+ ( s ) => s . setSelectedReportIds ,
116+ ) ;
113117 const toggleReportSelection = useInboxReportSelectionStore (
114118 ( s ) => s . toggleReportSelection ,
115119 ) ;
116120 const pruneSelection = useInboxReportSelectionStore ( ( s ) => s . pruneSelection ) ;
117121
122+ // When true, an empty store means "nothing selected" — no virtual fallback.
123+ // Set once the user first explicitly interacts with any checkbox.
124+ // Resets when the open report changes while the store is still empty (fresh report).
125+ const [ selectionExplicitlyActivated , setSelectionExplicitlyActivated ] =
126+ useState ( false ) ;
127+
128+ // Stable refs so callbacks don't need re-registration on every render
129+ const selectedReportIdsRef = useRef ( selectedReportIds ) ;
130+ selectedReportIdsRef . current = selectedReportIds ;
131+ const selectionExplicitlyActivatedRef = useRef ( false ) ;
132+ selectionExplicitlyActivatedRef . current = selectionExplicitlyActivated ;
133+
118134 useEffect ( ( ) => {
119135 if ( reports . length === 0 ) {
120136 setSelectedReportId ( null ) ;
@@ -135,11 +151,79 @@ export function InboxSignalsTab() {
135151 pruneSelection ( reports . map ( ( report ) => report . id ) ) ;
136152 } , [ reports , pruneSelection ] ) ;
137153
154+ // Reset to virtual mode when a different report is opened while the store is empty.
155+ // selectedReportIdsRef is read (not declared) in the callback — biome can't see it
156+ // depends on selectedReportId, so the dep is intentional.
157+ // biome-ignore lint/correctness/useExhaustiveDependencies: selectedReportId is the trigger; store length is read via a ref to avoid adding it as a dep
158+ useEffect ( ( ) => {
159+ if ( selectedReportIdsRef . current . length === 0 ) {
160+ setSelectionExplicitlyActivated ( false ) ;
161+ }
162+ } , [ selectedReportId ] ) ;
163+
138164 const selectedReport = useMemo (
139165 ( ) => reports . find ( ( report ) => report . id === selectedReportId ) ?? null ,
140166 [ reports , selectedReportId ] ,
141167 ) ;
142168
169+ const effectiveBulkIds = useMemo (
170+ ( ) =>
171+ getEffectiveBulkSelectionIds (
172+ selectedReportIds ,
173+ selectedReportId ,
174+ selectionExplicitlyActivated ,
175+ ) ,
176+ [ selectedReportIds , selectedReportId , selectionExplicitlyActivated ] ,
177+ ) ;
178+
179+ // Toggle a report's checkbox, handling the virtual → explicit mode transition.
180+ // When the first explicit toggle happens in virtual mode (store empty, a report is open):
181+ // - toggling a DIFFERENT report: seed the open report into the store too (keep it checked)
182+ // - toggling the OPEN report itself: transition to explicit-empty (uncheck it)
183+ const handleToggleReportSelection = useCallback (
184+ ( reportId : string ) => {
185+ if (
186+ ! selectionExplicitlyActivatedRef . current &&
187+ selectedReportIdsRef . current . length === 0
188+ ) {
189+ setSelectionExplicitlyActivated ( true ) ;
190+ if (
191+ selectedReportIdRef . current !== null &&
192+ reportId !== selectedReportIdRef . current
193+ ) {
194+ // Seed the open report + add the newly toggled one
195+ setSelectedReportIds ( [ selectedReportIdRef . current , reportId ] ) ;
196+ }
197+ // If toggling the open report's own checkbox, the store stays empty
198+ // and explicit = true → effective = [] (it becomes unchecked)
199+ } else {
200+ toggleReportSelection ( reportId ) ;
201+ }
202+ } ,
203+ [ setSelectedReportIds , toggleReportSelection ] ,
204+ ) ;
205+
206+ // Handle the select-all checkbox. Parent owns all state transitions.
207+ const handleToggleSelectAll = useCallback (
208+ ( checked : boolean ) => {
209+ if ( checked ) {
210+ setSelectedReportIds ( reportsRef . current . map ( ( r ) => r . id ) ) ;
211+ setSelectionExplicitlyActivated ( true ) ;
212+ } else {
213+ setSelectedReportIds ( [ ] ) ;
214+ if ( ! selectionExplicitlyActivatedRef . current ) {
215+ // Was in virtual mode (open report only virtually selected):
216+ // close the report so there is truly nothing selected.
217+ setSelectedReportId ( null ) ;
218+ setSelectionExplicitlyActivated ( false ) ;
219+ }
220+ // If already in explicit mode, keep the flag true so the empty store
221+ // means nothing selected — no fallback to the virtual open report.
222+ }
223+ } ,
224+ [ setSelectedReportIds ] ,
225+ ) ;
226+
143227 // ── Sidebar resize ─────────────────────────────────────────────────────
144228 const sidebarWidth = useInboxSignalsSidebarStore ( ( state ) => state . width ) ;
145229 const sidebarIsResizing = useInboxSignalsSidebarStore (
@@ -279,14 +363,17 @@ export function InboxSignalsTab() {
279363 } else if ( e . key === "ArrowUp" ) {
280364 e . preventDefault ( ) ;
281365 navigateReport ( - 1 ) ;
282- } else if ( e . key === " " && selectedReportIdRef . current ) {
366+ } else if (
367+ ( e . key === " " || e . key === "Enter" ) &&
368+ selectedReportIdRef . current
369+ ) {
283370 e . preventDefault ( ) ;
284- toggleReportSelection ( selectedReportIdRef . current ) ;
371+ handleToggleReportSelection ( selectedReportIdRef . current ) ;
285372 }
286373 } ;
287374 window . addEventListener ( "keydown" , handler ) ;
288375 return ( ) => window . removeEventListener ( "keydown" , handler ) ;
289- } , [ navigateReport , toggleReportSelection ] ) ;
376+ } , [ navigateReport , handleToggleReportSelection ] ) ;
290377
291378 const searchDisabledReason =
292379 ! hasReports && ! searchQuery . trim ( )
@@ -381,6 +468,8 @@ export function InboxSignalsTab() {
381468 readyCount = { readyCount }
382469 processingCount = { processingCount }
383470 reports = { reports }
471+ effectiveBulkIds = { effectiveBulkIds }
472+ onToggleSelectAll = { handleToggleSelectAll }
384473 />
385474 </ Box >
386475 < ReportListPane
@@ -397,9 +486,9 @@ export function InboxSignalsTab() {
397486 searchQuery = { searchQuery }
398487 hasActiveFilters = { hasActiveFilters }
399488 selectedReportId = { selectedReportId }
400- selectedReportIds = { selectedReportIds }
489+ selectedReportIds = { effectiveBulkIds }
401490 onSelectReport = { setSelectedReportId }
402- onToggleReportSelection = { toggleReportSelection }
491+ onToggleReportSelection = { handleToggleReportSelection }
403492 />
404493 </ Flex >
405494 </ ScrollArea >
0 commit comments