Skip to content

Commit 611711d

Browse files
committed
feat(queue): add pending/completed toggle with virtual scroll
- Add completed queue storage with persistence - Add dismiss_review_item and get_completed_queue commands - Implement virtualized scrolling with @tanstack/react-virtual - Add MiniSwitch to toggle between pending-only and all messages - Replace ClipboardList icon with Lovcode logo
1 parent a7765d4 commit 611711d

2 files changed

Lines changed: 329 additions & 83 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,50 @@ static SEARCH_INDEX: Mutex<Option<SearchIndex>> = Mutex::new(None);
103103
// Global review queue for notification server
104104
static 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
107151
static 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]
19022001
fn 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

Comments
 (0)