@@ -8,6 +8,7 @@ use std::time::Duration;
88use anyhow:: { Result , anyhow} ;
99use notify:: {
1010 Config as NotifyConfig , Event , EventKind , RecommendedWatcher , RecursiveMode , Watcher ,
11+ event:: { AccessKind , AccessMode } ,
1112} ;
1213use serde_json:: { Map , Value } ;
1314use tokio:: signal;
@@ -527,7 +528,15 @@ fn normalize_path(path: &Path) -> String {
527528fn is_relevant_event ( kind : & EventKind ) -> bool {
528529 matches ! (
529530 kind,
530- EventKind :: Create ( _) | EventKind :: Modify ( _) | EventKind :: Remove ( _) | EventKind :: Any
531+ EventKind :: Create ( _)
532+ | EventKind :: Modify ( _)
533+ | EventKind :: Remove ( _)
534+ | EventKind :: Any
535+ // Some backends collapse close-after-write saves into an imprecise close event,
536+ // so keep unknown close modes to avoid missing real file writes.
537+ | EventKind :: Access ( AccessKind :: Close (
538+ AccessMode :: Write | AccessMode :: Any | AccessMode :: Other
539+ ) )
531540 )
532541}
533542
@@ -607,6 +616,50 @@ mod tests {
607616 assert_eq ! ( grouped[ "content" ] , vec![ "watched.txt" ] ) ;
608617 }
609618
619+ #[ test]
620+ fn classify_changes_by_workflow_accepts_write_close_access_events ( ) {
621+ let root = PathBuf :: from ( "/tmp/example" ) ;
622+ let groups =
623+ vec ! [ CompiledWatchGroup :: for_test( & [ "tailwind.css" ] , "css" ) . expect( "watch group" ) ] ;
624+ let events = vec ! [
625+ Event {
626+ kind: EventKind :: Access ( AccessKind :: Close ( AccessMode :: Write ) ) ,
627+ paths: vec![ root. join( "tailwind.css" ) ] ,
628+ attrs: Default :: default ( ) ,
629+ } ,
630+ Event {
631+ kind: EventKind :: Access ( AccessKind :: Close ( AccessMode :: Any ) ) ,
632+ paths: vec![ root. join( "tailwind.css" ) ] ,
633+ attrs: Default :: default ( ) ,
634+ } ,
635+ Event {
636+ kind: EventKind :: Access ( AccessKind :: Close ( AccessMode :: Other ) ) ,
637+ paths: vec![ root. join( "tailwind.css" ) ] ,
638+ attrs: Default :: default ( ) ,
639+ } ,
640+ ] ;
641+
642+ let grouped = classify_events ( & root, & groups, & events) ;
643+
644+ assert_eq ! ( grouped[ "css" ] , vec![ "tailwind.css" ] ) ;
645+ }
646+
647+ #[ test]
648+ fn classify_changes_by_workflow_rejects_read_close_access_events ( ) {
649+ let root = PathBuf :: from ( "/tmp/example" ) ;
650+ let groups =
651+ vec ! [ CompiledWatchGroup :: for_test( & [ "tailwind.css" ] , "css" ) . expect( "watch group" ) ] ;
652+ let events = vec ! [ Event {
653+ kind: EventKind :: Access ( AccessKind :: Close ( AccessMode :: Read ) ) ,
654+ paths: vec![ root. join( "tailwind.css" ) ] ,
655+ attrs: Default :: default ( ) ,
656+ } ] ;
657+
658+ let grouped = classify_events ( & root, & groups, & events) ;
659+
660+ assert ! ( grouped. is_empty( ) ) ;
661+ }
662+
610663 #[ tokio:: test]
611664 async fn write_state_step_renders_session_template ( ) {
612665 let state_path = unique_state_path ( ) ;
0 commit comments