@@ -103,6 +103,50 @@ static SEARCH_INDEX: Mutex<Option<SearchIndex>> = Mutex::new(None);
103103// Global review queue for notification server
104104static REVIEW_QUEUE : LazyLock < Mutex < Vec < ReviewItem > > > = LazyLock :: new ( || Mutex :: new ( Vec :: new ( ) ) ) ;
105105
106+ // Global completed queue for dismissed items
107+ static COMPLETED_QUEUE : LazyLock < Mutex < Vec < ReviewItem > > > = LazyLock :: new ( || Mutex :: new ( Vec :: new ( ) ) ) ;
108+
109+ // Completed queue persistence path
110+ fn get_completed_queue_path ( ) -> PathBuf {
111+ dirs:: data_local_dir ( )
112+ . unwrap_or_else ( || PathBuf :: from ( "." ) )
113+ . join ( "lovcode" )
114+ . join ( "completed_queue.jsonl" )
115+ }
116+
117+ // Load completed queue from disk
118+ fn load_completed_queue ( ) {
119+ let path = get_completed_queue_path ( ) ;
120+ if path. exists ( ) {
121+ if let Ok ( content) = std:: fs:: read_to_string ( & path) {
122+ let mut queue = COMPLETED_QUEUE . lock ( ) . unwrap ( ) ;
123+ for line in content. lines ( ) {
124+ if let Ok ( item) = serde_json:: from_str :: < ReviewItem > ( line) {
125+ queue. push ( item) ;
126+ }
127+ }
128+ }
129+ }
130+ }
131+
132+ // Append a completed item to disk
133+ fn persist_completed_item ( item : & ReviewItem ) {
134+ let path = get_completed_queue_path ( ) ;
135+ if let Some ( parent) = path. parent ( ) {
136+ let _ = std:: fs:: create_dir_all ( parent) ;
137+ }
138+ if let Ok ( json) = serde_json:: to_string ( item) {
139+ use std:: io:: Write ;
140+ if let Ok ( mut file) = std:: fs:: OpenOptions :: new ( )
141+ . create ( true )
142+ . append ( true )
143+ . open ( & path)
144+ {
145+ let _ = writeln ! ( file, "{}" , json) ;
146+ }
147+ }
148+ }
149+
106150// Global auto-increment sequence number for review items
107151static REVIEW_SEQ : std:: sync:: atomic:: AtomicU64 = std:: sync:: atomic:: AtomicU64 :: new ( 1 ) ;
108152
@@ -1898,6 +1942,61 @@ fn get_review_queue() -> Vec<ReviewItem> {
18981942 REVIEW_QUEUE . lock ( ) . unwrap ( ) . clone ( )
18991943}
19001944
1945+ #[ tauri:: command]
1946+ fn get_completed_queue ( limit : Option < usize > , offset : Option < usize > ) -> Vec < ReviewItem > {
1947+ let queue = COMPLETED_QUEUE . lock ( ) . unwrap ( ) ;
1948+ let skip = offset. unwrap_or ( 0 ) ;
1949+ let take = limit. unwrap_or ( 50 ) ;
1950+ // Return newest first (reverse order since we append)
1951+ queue. iter ( ) . rev ( ) . skip ( skip) . take ( take) . cloned ( ) . collect ( )
1952+ }
1953+
1954+ #[ tauri:: command]
1955+ fn dismiss_review_item ( app_handle : tauri:: AppHandle , id : String ) -> Result < ( ) , String > {
1956+ let dismissed_item = {
1957+ let mut queue = REVIEW_QUEUE . lock ( ) . unwrap ( ) ;
1958+ let pos = queue. iter ( ) . position ( |item| item. id == id) ;
1959+ pos. map ( |i| queue. remove ( i) )
1960+ } ;
1961+
1962+ if let Some ( mut item) = dismissed_item {
1963+ // Update timestamp to completion time
1964+ item. timestamp = std:: time:: SystemTime :: now ( )
1965+ . duration_since ( std:: time:: UNIX_EPOCH )
1966+ . unwrap_or_default ( )
1967+ . as_secs ( ) ;
1968+
1969+ // Add to completed queue
1970+ {
1971+ let mut completed = COMPLETED_QUEUE . lock ( ) . unwrap ( ) ;
1972+ completed. push ( item. clone ( ) ) ;
1973+ }
1974+
1975+ // Persist to disk
1976+ persist_completed_item ( & item) ;
1977+
1978+ // Emit updated queues to frontend
1979+ let pending = REVIEW_QUEUE . lock ( ) . unwrap ( ) . clone ( ) ;
1980+ let _ = app_handle. emit ( "review-queue-update" , pending) ;
1981+ }
1982+
1983+ Ok ( ( ) )
1984+ }
1985+
1986+ #[ tauri:: command]
1987+ fn clear_completed_queue ( ) -> Result < ( ) , String > {
1988+ {
1989+ let mut queue = COMPLETED_QUEUE . lock ( ) . unwrap ( ) ;
1990+ queue. clear ( ) ;
1991+ }
1992+ // Remove persistence file
1993+ let path = get_completed_queue_path ( ) ;
1994+ if path. exists ( ) {
1995+ std:: fs:: remove_file ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1996+ }
1997+ Ok ( ( ) )
1998+ }
1999+
19012000#[ tauri:: command]
19022001fn list_distill_documents ( ) -> Result < Vec < DistillDocument > , String > {
19032002 let distill_dir = get_distill_dir ( ) ;
@@ -3181,6 +3280,9 @@ pub fn run() {
31813280 // Load persisted review sequence number
31823281 load_review_seq ( ) ;
31833282
3283+ // Load persisted completed queue
3284+ load_completed_queue ( ) ;
3285+
31843286 // Start notification HTTP server
31853287 start_notify_server ( app. handle ( ) . clone ( ) ) ;
31863288
@@ -3405,6 +3507,9 @@ pub fn run() {
34053507 get_reference_doc,
34063508 emit_review_queue,
34073509 get_review_queue,
3510+ get_completed_queue,
3511+ dismiss_review_item,
3512+ clear_completed_queue,
34083513 navigate_to_tmux_pane,
34093514 set_cursor,
34103515 get_cursor_position_in_window,
0 commit comments