@@ -264,6 +264,24 @@ pub fn start_session_watcher(
264264 }
265265}
266266
267+ /// Filter for picker watcher events.
268+ /// Triggers on any Create event (new project directory or new session file) and
269+ /// on Modify/Remove events that target a `.jsonl` file.
270+ /// Keeping Create broad is intentional: when Claude Code starts a session in a
271+ /// brand-new project, the OS may deliver a single directory-creation event
272+ /// before the JSONL file appears, and we must not miss that signal.
273+ fn picker_event_filter ( event : & notify:: Event ) -> bool {
274+ if matches ! ( event. kind, notify:: EventKind :: Create ( _) ) {
275+ return true ;
276+ }
277+ event. paths . iter ( ) . any ( |p| {
278+ p. file_name ( )
279+ . and_then ( |n| n. to_str ( ) )
280+ . map ( |n| n. ends_with ( ".jsonl" ) )
281+ . unwrap_or ( false )
282+ } )
283+ }
284+
267285/// Start watching project directories for new/changed sessions.
268286/// When changes are detected the watcher broadcasts a lightweight `picker-refresh`
269287/// signal with no payload. Clients are responsible for fetching the updated
@@ -312,17 +330,7 @@ pub fn start_picker_watcher(
312330 }
313331 }
314332
315- run_debounce_loop (
316- rx,
317- |event| {
318- event. paths . iter ( ) . any ( |p| {
319- let name = p. file_name ( ) . and_then ( |n| n. to_str ( ) ) . unwrap_or ( "" ) ;
320- name. ends_with ( ".jsonl" )
321- } )
322- } ,
323- signal_tx,
324- thread_stop_rx,
325- ) ;
333+ run_debounce_loop ( rx, picker_event_filter, signal_tx, thread_stop_rx) ;
326334 // watcher dropped here → OS watcher fd released
327335 } ) ;
328336
@@ -394,6 +402,50 @@ mod tests {
394402 . expect ( "debounce thread should exit when stop sender is dropped" ) ;
395403 }
396404
405+ /// picker_event_filter triggers on Create events regardless of path extension.
406+ #[ test]
407+ fn picker_filter_triggers_on_create_events ( ) {
408+ // New project directory (no .jsonl extension)
409+ let mut event =
410+ notify:: Event :: new ( notify:: EventKind :: Create ( notify:: event:: CreateKind :: Folder ) ) ;
411+ event. paths = vec ! [ std:: path:: PathBuf :: from(
412+ "/home/.claude/projects/-Users-yang-new-project" ,
413+ ) ] ;
414+ assert ! ( picker_event_filter( & event) , "Create(Folder) must trigger" ) ;
415+
416+ // Any create — some platforms report CreateKind::Any
417+ let mut event =
418+ notify:: Event :: new ( notify:: EventKind :: Create ( notify:: event:: CreateKind :: Any ) ) ;
419+ event. paths = vec ! [ std:: path:: PathBuf :: from(
420+ "/home/.claude/projects/-Users-yang-new-project" ,
421+ ) ] ;
422+ assert ! ( picker_event_filter( & event) , "Create(Any) must trigger" ) ;
423+ }
424+
425+ /// picker_event_filter triggers on Modify events only for .jsonl files.
426+ #[ test]
427+ fn picker_filter_triggers_on_jsonl_modify ( ) {
428+ let mut event = notify:: Event :: new ( notify:: EventKind :: Modify (
429+ notify:: event:: ModifyKind :: Data ( notify:: event:: DataChange :: Any ) ,
430+ ) ) ;
431+ event. paths = vec ! [ std:: path:: PathBuf :: from(
432+ "/home/.claude/projects/proj/session.jsonl" ,
433+ ) ] ;
434+ assert ! ( picker_event_filter( & event) ) ;
435+ }
436+
437+ /// picker_event_filter ignores Modify events on non-.jsonl files.
438+ #[ test]
439+ fn picker_filter_ignores_non_jsonl_modify ( ) {
440+ let mut event = notify:: Event :: new ( notify:: EventKind :: Modify (
441+ notify:: event:: ModifyKind :: Data ( notify:: event:: DataChange :: Any ) ,
442+ ) ) ;
443+ event. paths = vec ! [ std:: path:: PathBuf :: from(
444+ "/home/.claude/projects/proj/settings.json" ,
445+ ) ] ;
446+ assert ! ( !picker_event_filter( & event) ) ;
447+ }
448+
397449 /// WatcherHandle::stop() must not panic when called multiple times on a closed channel.
398450 #[ test]
399451 fn watcher_handle_stop_is_idempotent ( ) {
0 commit comments