fix(m1): address HIGH review findings — journald zombie, silent exit, double-delivery
This commit is contained in:
parent
db3694d9cf
commit
d8991905d7
10 changed files with 46 additions and 10 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -29,3 +29,6 @@ src-tauri/target/
|
|||
# Secrets
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Visual companion brainstorm sessions
|
||||
.superpowers/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,3 +69,13 @@ pub fn update_notification_level(level: String, state: State<'_, AppState>) -> R
|
|||
pub fn get_pending_events() -> Vec<crate::patterns::MatchedEvent> {
|
||||
crate::notify::take_pending()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn panel_opened() {
|
||||
crate::notify::set_panel_open(true);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn panel_closed() {
|
||||
crate::notify::set_panel_open(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,8 +102,7 @@ impl RobinConfig {
|
|||
}
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read {}", path.display()))?;
|
||||
toml::from_str(&content)
|
||||
.with_context(|| format!("failed to parse {}", path.display()))
|
||||
toml::from_str(&content).with_context(|| format!("failed to parse {}", path.display()))
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ pub fn run() {
|
|||
commands::complete_onboarding,
|
||||
commands::update_notification_level,
|
||||
commands::get_pending_events,
|
||||
commands::panel_opened,
|
||||
commands::panel_closed,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Robin");
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
app_lib::run();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, Emitter, Runtime};
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
|
|
@ -7,6 +8,13 @@ use crate::config::NotificationLevel;
|
|||
use crate::patterns::MatchedEvent;
|
||||
|
||||
static PENDING: Mutex<Vec<MatchedEvent>> = Mutex::new(vec![]);
|
||||
// True while ChatPanel is mounted and listening for live events.
|
||||
// When true, dispatch skips PENDING so events are not shown twice on re-open.
|
||||
static PANEL_OPEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set_panel_open(open: bool) {
|
||||
PANEL_OPEN.store(open, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn dispatch<R: Runtime>(app: &AppHandle<R>, event: MatchedEvent) {
|
||||
let level = app
|
||||
|
|
@ -16,9 +24,11 @@ pub fn dispatch<R: Runtime>(app: &AppHandle<R>, event: MatchedEvent) {
|
|||
.map(|c| c.display.notification_level.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
match PENDING.lock() {
|
||||
Ok(mut pending) => pending.push(event.clone()),
|
||||
Err(e) => log::warn!("notify: pending queue lock poisoned — event dropped: {e}"),
|
||||
if !PANEL_OPEN.load(Ordering::Relaxed) {
|
||||
match PENDING.lock() {
|
||||
Ok(mut pending) => pending.push(event.clone()),
|
||||
Err(e) => log::warn!("notify: pending queue lock poisoned — event dropped: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
match level {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::{EventSource, SystemEvent, now_unix};
|
||||
use super::{now_unix, EventSource, SystemEvent};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::mpsc;
|
||||
|
|
@ -33,6 +33,9 @@ pub async fn watch(tx: mpsc::Sender<SystemEvent>) {
|
|||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("journald watcher: journalctl exited — no new journal events will be observed");
|
||||
let _ = child.wait().await;
|
||||
}
|
||||
|
||||
fn extract_message(line: &str) -> Option<String> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::{EventSource, SystemEvent, now_unix};
|
||||
use super::{now_unix, EventSource, SystemEvent};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::sync::mpsc;
|
||||
|
|
@ -54,7 +54,10 @@ mod tests {
|
|||
#[test]
|
||||
fn parses_kmsg_line_without_semicolon() {
|
||||
let line = "plain message without header";
|
||||
assert_eq!(parse_kmsg(line), Some("plain message without header".to_string()));
|
||||
assert_eq!(
|
||||
parse_kmsg(line),
|
||||
Some("plain message without header".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ const messagesEl = ref<HTMLElement | null>(null)
|
|||
let unlisten: UnlistenFn | null = null
|
||||
|
||||
onMounted(async () => {
|
||||
// Signal backend: stop queuing events into PENDING while panel is open.
|
||||
// Must happen before drain so new events that arrive during mount are not
|
||||
// double-delivered on the next open cycle.
|
||||
try { await invoke('panel_opened') } catch {}
|
||||
|
||||
try {
|
||||
const pending = await invoke<RobinEvent[]>('get_pending_events')
|
||||
for (const e of pending) {
|
||||
|
|
@ -63,6 +68,7 @@ onMounted(async () => {
|
|||
|
||||
onUnmounted(() => {
|
||||
unlisten?.()
|
||||
invoke('panel_closed').catch(() => {})
|
||||
})
|
||||
|
||||
function pushRobinEvent(e: RobinEvent) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue