fix(review): bugs, arch violations, design smells

P1 bugs:
- unix_launcher: shell_split respects quoted args (was split_whitespace)
- plugin-host: 5s timeout on external plugin search
- ui: handle engine init panic, wire error state
- ui-egui: read window config instead of always using defaults
- plugin-url: use OpenPath action instead of SpawnProcess+xdg-open

Architecture:
- remove WindowConfig (mirror of WindowCfg); use WindowCfg directly
- remove on_select closure from SearchResult (domain leakage)
- remove LaunchAction::Custom; add Plugin::on_selected + SearchEngine::on_selected
- apps: record frecency via on_selected instead of embedded closure

Design smells:
- frecency: extract decay_factor helper, write outside mutex
- apps: remove cfg(test) cache_path hack; add new_for_test ctor
- apps: stable ResultId using name+exec to prevent collision
- files: stable ResultId using full path instead of index
- plugin-host: remove k-launcher-os-bridge dep (WindowConfig gone)
This commit is contained in:
2026-03-18 13:45:48 +01:00
parent 38860762c0
commit ff9b2b5712
18 changed files with 189 additions and 133 deletions

View File

@@ -50,7 +50,6 @@ pub enum LaunchAction {
SpawnInTerminal(String),
OpenPath(String),
CopyToClipboard(String),
Custom(Arc<dyn Fn() + Send + Sync>),
}
// --- AppLauncher port trait ---
@@ -68,7 +67,6 @@ pub struct SearchResult {
pub icon: Option<String>,
pub score: Score,
pub action: LaunchAction,
pub on_select: Option<Arc<dyn Fn() + Send + Sync>>,
}
impl std::fmt::Debug for SearchResult {
@@ -88,6 +86,7 @@ impl std::fmt::Debug for SearchResult {
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
async fn search(&self, query: &str) -> Vec<SearchResult>;
fn on_selected(&self, _id: &ResultId) {}
}
// --- SearchEngine port trait ---
@@ -95,6 +94,7 @@ pub trait Plugin: Send + Sync {
#[async_trait]
pub trait SearchEngine: Send + Sync {
async fn search(&self, query: &str) -> Vec<SearchResult>;
fn on_selected(&self, id: &ResultId);
}
// --- NullSearchEngine ---
@@ -106,6 +106,7 @@ impl SearchEngine for NullSearchEngine {
async fn search(&self, _query: &str) -> Vec<SearchResult> {
vec![]
}
fn on_selected(&self, _id: &ResultId) {}
}
// --- Kernel (Application use case) ---
@@ -123,6 +124,12 @@ impl Kernel {
}
}
pub fn on_selected(&self, id: &ResultId) {
for plugin in &self.plugins {
plugin.on_selected(id);
}
}
pub async fn search(&self, query: &str) -> Vec<SearchResult> {
use futures::FutureExt;
use std::panic::AssertUnwindSafe;
@@ -154,6 +161,9 @@ impl SearchEngine for Kernel {
async fn search(&self, query: &str) -> Vec<SearchResult> {
self.search(query).await
}
fn on_selected(&self, id: &ResultId) {
self.on_selected(id);
}
}
// --- Tests ---
@@ -188,8 +198,7 @@ mod tests {
description: None,
icon: None,
score: Score::new(*score),
action: LaunchAction::Custom(Arc::new(|| {})),
on_select: None,
action: LaunchAction::SpawnProcess("mock".to_string()),
})
.collect()
}