@@ -8,6 +8,7 @@ import { InboxLiveRail } from "@features/inbox/components/InboxLiveRail";
88import { InboxSourcesDialog } from "@features/inbox/components/InboxSourcesDialog" ;
99import { useInboxReportsInfinite } from "@features/inbox/hooks/useInboxReports" ;
1010import { useSignalSourceConfigs } from "@features/inbox/hooks/useSignalSourceConfigs" ;
11+ import { useInboxReportSelectionStore } from "@features/inbox/stores/inboxReportSelectionStore" ;
1112import { useInboxSignalsFilterStore } from "@features/inbox/stores/inboxSignalsFilterStore" ;
1213import { useInboxSignalsSidebarStore } from "@features/inbox/stores/inboxSignalsSidebarStore" ;
1314import { useInboxSourcesDialogStore } from "@features/inbox/stores/inboxSourcesDialogStore" ;
@@ -107,6 +108,13 @@ export function InboxSignalsTab() {
107108
108109 // ── Selection state ─────────────────────────────────────────────────────
109110 const [ selectedReportId , setSelectedReportId ] = useState < string | null > ( null ) ;
111+ const selectedReportIds = useInboxReportSelectionStore (
112+ ( s ) => s . selectedReportIds ?? [ ] ,
113+ ) ;
114+ const toggleReportSelection = useInboxReportSelectionStore (
115+ ( s ) => s . toggleReportSelection ,
116+ ) ;
117+ const pruneSelection = useInboxReportSelectionStore ( ( s ) => s . pruneSelection ) ;
110118
111119 useEffect ( ( ) => {
112120 if ( reports . length === 0 ) {
@@ -124,6 +132,10 @@ export function InboxSignalsTab() {
124132 }
125133 } , [ reports , selectedReportId ] ) ;
126134
135+ useEffect ( ( ) => {
136+ pruneSelection ( reports . map ( ( report ) => report . id ) ) ;
137+ } , [ reports , pruneSelection ] ) ;
138+
127139 const selectedReport = useMemo (
128140 ( ) => reports . find ( ( report ) => report . id === selectedReportId ) ?? null ,
129141 [ reports , selectedReportId ] ,
@@ -201,19 +213,24 @@ export function InboxSignalsTab() {
201213 selectedReportIdRef . current = selectedReportId ;
202214 const leftPaneRef = useRef < HTMLDivElement > ( null ) ;
203215
216+ const focusListPane = useCallback ( ( ) => {
217+ requestAnimationFrame ( ( ) => {
218+ leftPaneRef . current ?. focus ( ) ;
219+ } ) ;
220+ } , [ ] ) ;
221+
204222 // Auto-focus the list pane when the two-pane layout appears
205223 useEffect ( ( ) => {
206224 if ( showTwoPaneLayout ) {
207225 // Small delay to ensure the ref is mounted after conditional render
208- requestAnimationFrame ( ( ) => {
209- leftPaneRef . current ?. focus ( ) ;
210- } ) ;
226+ focusListPane ( ) ;
211227 }
212- } , [ showTwoPaneLayout ] ) ;
228+ } , [ focusListPane , showTwoPaneLayout ] ) ;
213229
214230 const navigateReport = useCallback ( ( direction : 1 | - 1 ) => {
215231 const list = reportsRef . current ;
216232 if ( list . length === 0 ) return ;
233+
217234 const currentId = selectedReportIdRef . current ;
218235 const currentIndex = currentId
219236 ? list . findIndex ( ( r ) => r . id === currentId )
@@ -223,10 +240,22 @@ export function InboxSignalsTab() {
223240 ? 0
224241 : Math . max ( 0 , Math . min ( list . length - 1 , currentIndex + direction ) ) ;
225242 const nextId = list [ nextIndex ] . id ;
243+
226244 setSelectedReportId ( nextId ) ;
227- leftPaneRef . current
228- ?. querySelector ( `[data-report-id="${ nextId } "]` )
229- ?. scrollIntoView ( { block : "nearest" } ) ;
245+
246+ const container = leftPaneRef . current ;
247+ const row = container ?. querySelector < HTMLElement > (
248+ `[data-report-id="${ nextId } "]` ,
249+ ) ;
250+ const stickyHeader = container ?. querySelector < HTMLElement > (
251+ "[data-inbox-sticky-header]" ,
252+ ) ;
253+
254+ if ( ! row ) return ;
255+
256+ const stickyHeaderHeight = stickyHeader ?. offsetHeight ?? 0 ;
257+ row . style . scrollMarginTop = `${ stickyHeaderHeight } px` ;
258+ row . scrollIntoView ( { block : "nearest" } ) ;
230259 } , [ ] ) ;
231260
232261 // Window-level keyboard handler so arrow keys work regardless of which
@@ -243,18 +272,22 @@ export function InboxSignalsTab() {
243272
244273 const target = e . target as HTMLElement ;
245274 if ( target . closest ( "input, select, textarea" ) ) return ;
275+ if ( e . key === " " && target . closest ( "button, [role='checkbox']" ) ) return ;
246276
247277 if ( e . key === "ArrowDown" ) {
248278 e . preventDefault ( ) ;
249279 navigateReport ( 1 ) ;
250280 } else if ( e . key === "ArrowUp" ) {
251281 e . preventDefault ( ) ;
252282 navigateReport ( - 1 ) ;
283+ } else if ( e . key === " " && selectedReportIdRef . current ) {
284+ e . preventDefault ( ) ;
285+ toggleReportSelection ( selectedReportIdRef . current ) ;
253286 }
254287 } ;
255288 window . addEventListener ( "keydown" , handler ) ;
256289 return ( ) => window . removeEventListener ( "keydown" , handler ) ;
257- } , [ navigateReport ] ) ;
290+ } , [ navigateReport , toggleReportSelection ] ) ;
258291
259292 const searchDisabledReason =
260293 ! hasReports && ! searchQuery . trim ( )
@@ -287,11 +320,33 @@ export function InboxSignalsTab() {
287320 < Flex
288321 ref = { leftPaneRef }
289322 direction = "column"
290- tabIndex = { - 1 }
323+ tabIndex = { 0 }
291324 className = "outline-none"
325+ onMouseDownCapture = { ( e ) => {
326+ const target = e . target as HTMLElement ;
327+ if (
328+ target . closest (
329+ "[data-report-id], button, input, select, textarea, [role='checkbox']" ,
330+ )
331+ ) {
332+ focusListPane ( ) ;
333+ }
334+ } }
335+ onFocusCapture = { ( e ) => {
336+ const target = e . target as HTMLElement ;
337+ if (
338+ target !== leftPaneRef . current &&
339+ target . closest (
340+ "[data-report-id], button, input, select, textarea, [role='checkbox']" ,
341+ )
342+ ) {
343+ focusListPane ( ) ;
344+ }
345+ } }
292346 >
293347 < InboxLiveRail active = { inboxPollingActive } />
294348 < Box
349+ data-inbox-sticky-header
295350 style = { {
296351 position : "sticky" ,
297352 top : 0 ,
@@ -306,6 +361,7 @@ export function InboxSignalsTab() {
306361 livePolling = { inboxPollingActive }
307362 readyCount = { readyCount }
308363 processingCount = { processingCount }
364+ reports = { reports }
309365 />
310366 </ Box >
311367 < ReportListPane
@@ -322,7 +378,9 @@ export function InboxSignalsTab() {
322378 searchQuery = { searchQuery }
323379 hasActiveFilters = { hasActiveFilters }
324380 selectedReportId = { selectedReportId }
381+ selectedReportIds = { selectedReportIds }
325382 onSelectReport = { setSelectedReportId }
383+ onToggleReportSelection = { toggleReportSelection }
326384 />
327385 </ Flex >
328386 </ ScrollArea >
0 commit comments