refactor(desktop): encapsulate settings store status

This commit is contained in:
Kang
2026-07-29 17:40:09 +08:00
parent f393752b78
commit d3fbfd9913
33 changed files with 366 additions and 234 deletions
@@ -180,8 +180,9 @@ impl NyaTermApp {
cx.spawn(async move |this, cx| {
if let Err(error) = task.await {
let _ = this.update(cx, |this, cx| {
this.settings.store_status.message = format!("AI audit save failed: {error}");
this.settings.store_status.ready = false;
this.settings
.set_store_message(format!("AI audit save failed: {error}"));
this.settings.set_store_ready(false);
cx.notify();
});
} else {
@@ -85,14 +85,16 @@ impl NyaTermApp {
Ok(discoveries) => {
let count = self.apply_ai_model_discoveries(&event.profile_id, discoveries);
self.ai.panel.status = format!("Discovered {count} AI model(s)");
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(true);
self.persist_ai_settings_now(cx);
}
Err(error) => {
self.ai.panel.status = format!("AI model discovery failed: {error}");
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(false);
}
}
}
@@ -75,7 +75,8 @@ impl NyaTermApp {
"AI Agent request was cancelled",
);
}
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings
.set_store_message(self.ai.panel.status.clone());
cx.notify();
}
@@ -790,9 +791,9 @@ impl NyaTermApp {
} else {
"AI stream receiving".to_string()
};
self.settings.store_status.message =
format!("AI session {session_id} streaming");
self.settings.store_status.ready = true;
self.settings
.set_store_message(format!("AI session {session_id} streaming"));
self.settings.set_store_ready(true);
}
AiChatWorkerEvent::AgentToolCallDelta {
job_id,
@@ -832,9 +833,10 @@ impl NyaTermApp {
format!("Streaming arguments (+{arguments_delta_len} chars)")
},
);
self.settings.store_status.message =
format!("AI session {session_id} streaming Agent tool call");
self.settings.store_status.ready = true;
self.settings.set_store_message(format!(
"AI session {session_id} streaming Agent tool call"
));
self.settings.set_store_ready(true);
}
AiChatWorkerEvent::AgentBackgroundFinished {
job_id,
@@ -879,8 +881,9 @@ impl NyaTermApp {
"Failed",
truncate_preview(&error, 140),
);
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(false);
}
}
}
@@ -958,9 +961,11 @@ impl NyaTermApp {
message.command_cards = output.command_cards.clone();
}
}
self.settings.store_status.message =
format!("AI session {} updated", event.session_id);
self.settings.store_status.ready = true;
self.settings.set_store_message(format!(
"AI session {} updated",
event.session_id
));
self.settings.set_store_ready(true);
self.reset_text_input("ai.chat.prompt", "", cx);
self.ai.chat.prompt_draft.clear();
self.refresh_ai_usage_counts(cx);
@@ -1007,8 +1012,9 @@ impl NyaTermApp {
truncate_preview(&error, 140),
);
}
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(false);
}
}
}
@@ -125,13 +125,15 @@ impl NyaTermApp {
if self.ai.panel.status.trim().is_empty() {
self.ai.panel.status = "AI settings saved".to_string();
}
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.ai.panel.status = format!("AI settings save failed: {error}");
self.settings.store_status.message = self.ai.panel.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.ai.panel.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -14,7 +14,7 @@ use nyaterm_transport::{SessionManager, SftpDuplicatePolicy};
use std::collections::HashMap;
use std::sync::Arc;
use super::super::settings::{SettingsFeatureFocus, SettingsFeatureState, StoreStatus};
use super::super::settings::{SettingsFeatureFocus, SettingsFeatureState};
use super::super::{
AiFeatureFocus, AiFeatureState, CloudSyncFeatureState, CommandFeatureInit, CommandFeatureState,
ConnectionCatalogState, ConnectionFeatureFocus, ConnectionFeatureState,
@@ -111,11 +111,7 @@ impl NyaTermApp {
command_history,
keyword_highlights,
settings,
StoreStatus {
path,
message: "redb connection store online".to_string(),
ready: true,
},
(path, "redb connection store online".to_string(), true),
cloud_sync_settings,
cloud_sync_state,
translation_settings,
@@ -141,11 +137,7 @@ impl NyaTermApp {
Vec::new(),
KeywordHighlightConfig::default(),
AppSettingsSummary::default(),
StoreStatus {
path,
message: format!("failed to load sessions: {error}"),
ready: false,
},
(path, format!("failed to load sessions: {error}"), false),
CloudSyncSettings::default(),
CloudSyncState::default(),
TranslationSettings::default(),
@@ -172,15 +164,15 @@ impl NyaTermApp {
Vec::new(),
KeywordHighlightConfig::default(),
AppSettingsSummary::default(),
StoreStatus {
path: runtime
(
runtime
.config_dir()
.join("nyaterm.redb")
.display()
.to_string(),
message: format!("failed to open store: {error}"),
ready: false,
},
format!("failed to open store: {error}"),
false,
),
CloudSyncSettings::default(),
CloudSyncState::default(),
TranslationSettings::default(),
@@ -431,7 +423,9 @@ impl NyaTermApp {
settings: SettingsFeatureState::new(
settings,
keyword_highlights,
store_status,
store_status.0,
store_status.1,
store_status.2,
appearance_ui_font_options,
appearance_terminal_font_options,
SettingsFeatureFocus {
@@ -267,17 +267,17 @@ impl NyaTermApp {
}
}
if !self.commands.queue_command_history(submitted) {
self.settings.store_status.message =
"command history worker is unavailable".to_string();
self.settings.store_status.ready = false;
self.settings
.set_store_message("command history worker is unavailable".to_string());
self.settings.set_store_ready(false);
}
}
pub(in crate::features) fn queue_quick_command_use_count(&mut self, command_id: String) {
if !self.commands.queue_quick_command_use_count(command_id) {
self.settings.store_status.message =
"command persistence worker is unavailable".to_string();
self.settings.store_status.ready = false;
self.settings
.set_store_message("command persistence worker is unavailable".to_string());
self.settings.set_store_ready(false);
}
}
@@ -289,9 +289,10 @@ impl NyaTermApp {
CommandPersistencePoll::Empty => break,
CommandPersistencePoll::Disconnected { had_pending } => {
if had_pending {
self.settings.store_status.message =
"command persistence worker disconnected".to_string();
self.settings.store_status.ready = false;
self.settings.set_store_message(
"command persistence worker disconnected".to_string(),
);
self.settings.set_store_ready(false);
dirty = true;
}
break;
@@ -299,8 +300,8 @@ impl NyaTermApp {
};
dirty = true;
if let Err(message) = self.commands.apply_persistence_result(event) {
self.settings.store_status.message = message;
self.settings.store_status.ready = false;
self.settings.set_store_message(message);
self.settings.set_store_ready(false);
}
}
dirty
@@ -23,9 +23,9 @@ impl NyaTermApp {
.replace_quick_command_catalog(config.commands, config.categories);
}
Err(error) => {
self.settings.store_status.message =
format!("quick command refresh failed: {error}");
self.settings.store_status.ready = false;
self.settings
.set_store_message(format!("quick command refresh failed: {error}"));
self.settings.set_store_ready(false);
}
}
}
@@ -61,15 +61,16 @@ impl NyaTermApp {
{
Ok(settings) => {
self.apply_gpui_settings(settings);
self.settings.store_status.message = "quick command UI settings saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("quick command UI settings saved".to_string());
self.settings.set_store_ready(true);
self.terminal.view.status = "quick command UI settings saved".to_string();
}
Err(error) => {
self.settings.store_status.message =
format!("quick command UI settings save failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command UI settings save failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -153,19 +153,19 @@ impl NyaTermApp {
self.commands
.replace_quick_command_catalog(config.commands, config.categories);
self.commands.clear_quick_delete();
self.settings.store_status.message = if deleted {
self.settings.set_store_message(if deleted {
format!("quick command '{}' deleted", delete.label)
} else {
format!("quick command '{}' was already deleted", delete.label)
};
self.settings.store_status.ready = deleted;
self.terminal.view.status = self.settings.store_status.message.clone();
});
self.settings.set_store_ready(deleted);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
Err(error) => {
self.settings.store_status.message =
format!("quick command delete failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command delete failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -242,7 +242,7 @@ impl NyaTermApp {
self.commands
.replace_quick_command_catalog(config.commands, config.categories);
self.commands.finish_quick_category_delete(&delete.id);
self.settings.store_status.message = if deleted_category {
self.settings.set_store_message(if deleted_category {
format!(
"quick command category '{}' deleted with {} command(s)",
delete.name, deleted_commands
@@ -252,15 +252,15 @@ impl NyaTermApp {
"quick command category '{}' was already deleted",
delete.name
)
};
self.settings.store_status.ready = deleted_category;
self.terminal.view.status = self.settings.store_status.message.clone();
});
self.settings.set_store_ready(deleted_category);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
Err(error) => {
self.settings.store_status.message =
format!("quick command category delete failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command category delete failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -362,28 +362,29 @@ impl NyaTermApp {
.replace_quick_command_catalog(config.commands, config.categories);
if renamed {
self.commands.clear_quick_category_rename();
self.settings.store_status.message = format!(
self.settings.set_store_message(format!(
"quick command category '{}' renamed to '{}'",
rename.original_name, name
);
self.settings.store_status.ready = true;
));
self.settings.set_store_ready(true);
} else {
self.commands.set_quick_category_rename_error(
"Category is no longer available".to_string(),
);
self.settings.store_status.message =
"quick command category rename failed: category missing".to_string();
self.settings.store_status.ready = false;
self.settings.set_store_message(
"quick command category rename failed: category missing".to_string(),
);
self.settings.set_store_ready(false);
}
self.terminal.view.status = self.settings.store_status.message.clone();
self.terminal.view.status = self.settings.store_status().message.to_string();
}
Err(error) => {
self.commands
.set_quick_category_rename_error(error.to_string());
self.settings.store_status.message =
format!("quick command category rename failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command category rename failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -180,17 +180,18 @@ impl NyaTermApp {
self.commands
.replace_quick_command_catalog(config.commands, config.categories);
self.commands.close_quick_editor();
self.settings.store_status.message =
format!("quick command '{}' saved", command.label);
self.settings.store_status.ready = true;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command '{}' saved", command.label));
self.settings.set_store_ready(true);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
Err(error) => {
self.commands
.set_quick_editor_error(error.to_string(), None);
self.settings.store_status.message = format!("quick command save failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("quick command save failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -112,16 +112,18 @@ impl NyaTermApp {
self.terminal.view.status = format!(
"imported {imported_commands} quick command(s), updated {updated_commands}, categories +{imported_categories}, total {total_commands}/{total_categories}"
);
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
QuickCommandImportPathPromptResult::Cancelled => {
self.terminal.view.status = "quick command import cancelled".to_string();
}
QuickCommandImportPathPromptResult::Failed(error) => {
self.terminal.view.status = format!("quick command import failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
QuickCommandImportPathPromptResult::Closed => {
self.terminal.view.status =
@@ -126,14 +126,16 @@ impl NyaTermApp {
"Saved AI command card '{}' to Quick Commands",
label
));
self.settings.store_status.message = self.ai.panel_status().to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.ai.panel_status().to_string());
self.settings.set_store_ready(true);
}
Err(error) => {
self.ai
.set_panel_status(format!("Quick command save failed: {error}"));
self.settings.store_status.message = self.ai.panel_status().to_string();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.ai.panel_status().to_string());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -147,8 +147,8 @@ impl NyaTermApp {
.tr("savedConnections.importSuccess")
.replace("{{count}}", &count.to_string());
self.terminal.view.status = message.clone();
self.settings.store_status.message = message;
self.settings.store_status.ready = true;
self.settings.set_store_message(message);
self.settings.set_store_ready(true);
}
ConnectionImportResult::Cancelled => {
self.terminal.view.status = "connection import cancelled".to_string();
@@ -158,8 +158,8 @@ impl NyaTermApp {
.tr("savedConnections.importFailed")
.replace("{{error}}", &error);
self.terminal.view.status = message.clone();
self.settings.store_status.message = message;
self.settings.store_status.ready = false;
self.settings.set_store_message(message);
self.settings.set_store_ready(false);
}
ConnectionImportResult::Closed => {
self.terminal.view.status = "connection import picker closed".to_string();
@@ -38,8 +38,9 @@ impl NyaTermApp {
Err(error) => {
self.connection_state.close_clear_all();
self.terminal.view.status = format!("clear saved connections failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -67,8 +67,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("reorder connection failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
self.connection_state.clear_list_drop_target();
@@ -139,8 +140,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("reorder connection failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
self.connection_state.clear_list_drop_target();
@@ -190,8 +192,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("move connection failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -238,8 +241,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("move connections failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -70,8 +70,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("copy connection failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -54,8 +54,9 @@ impl NyaTermApp {
}
Err(error) => {
self.terminal.view.status = format!("copy selected connections failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -94,8 +95,9 @@ impl NyaTermApp {
.map_err(|error| error.to_string())?
.connections,
);
self.settings.store_status.message = "saved connections copied".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("saved connections copied".to_string());
self.settings.set_store_ready(true);
Ok(connections.len())
}
}
@@ -358,7 +358,7 @@ impl NyaTermApp {
div()
.rounded_md()
.border_1()
.border_color(if self.settings.store_status.ready {
.border_color(if self.settings.store_status().ready {
rgb(palette.hover)
} else {
rgb(palette.hover)
@@ -375,19 +375,19 @@ impl NyaTermApp {
div()
.mt_1()
.text_sm()
.text_color(if self.settings.store_status.ready {
.text_color(if self.settings.store_status().ready {
rgb(palette.success)
} else {
rgb(palette.danger)
})
.child(self.settings.store_status.message.clone()),
.child(self.settings.store_status().message.to_string()),
)
.child(
div()
.mt_2()
.text_xs()
.text_color(rgb(palette.text_muted))
.child(self.settings.store_status.path.clone()),
.child(self.settings.store_status().path.to_string()),
),
)
}
@@ -331,15 +331,16 @@ impl NyaTermApp {
{
Ok(settings) => {
self.apply_gpui_settings(settings);
self.settings.store_status.message = "file explorer favorites saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("file explorer favorites saved".to_string());
self.settings.set_store_ready(true);
}
Err(error) => {
self.settings.store_status.message =
format!("file explorer favorites save failed: {error}");
self.settings.store_status.ready = false;
self.settings
.set_store_message(format!("file explorer favorites save failed: {error}"));
self.settings.set_store_ready(false);
self.transfer
.set_browser_status(self.settings.store_status.message.clone());
.set_browser_status(self.settings.store_status().message.to_string());
}
}
}
@@ -37,10 +37,10 @@ impl SettingsMasterPasswordState {
}
#[derive(Debug, Clone)]
pub(in crate::features) struct StoreStatus {
pub path: String,
pub message: String,
pub ready: bool,
pub(super) struct StoreStatus {
pub(super) path: String,
pub(super) message: String,
pub(super) ready: bool,
}
#[cfg(test)]
@@ -10,8 +10,6 @@ use crate::models::{
TranslationSecretDraft,
};
use super::StoreStatus;
impl NyaTermApp {
pub(in crate::features) fn prompt_config_export(&mut self, cx: &mut Context<Self>) {
if !self
@@ -592,11 +590,11 @@ impl NyaTermApp {
.set_duplicate_policy(SftpDuplicatePolicy::from_legacy_value(
&self.settings.summary.transfer_duplicate_strategy,
));
self.settings.store_status = StoreStatus {
self.settings.replace_store_status(
path,
message: "redb connection store online".to_string(),
ready: true,
};
"redb connection store online".to_string(),
true,
);
}
Err(error) => {
self.connection_catalog.clear_loaded();
@@ -609,11 +607,11 @@ impl NyaTermApp {
TranslationSettings::default(),
TranslationSecretDraft::default(),
);
self.settings.store_status = StoreStatus {
self.settings.replace_store_status(
path,
message: format!("failed to load sessions: {error}"),
ready: false,
};
format!("failed to load sessions: {error}"),
false,
);
}
}
}
@@ -626,16 +624,15 @@ impl NyaTermApp {
TranslationSettings::default(),
TranslationSecretDraft::default(),
);
self.settings.store_status = StoreStatus {
path: self
.runtime
self.settings.replace_store_status(
self.runtime
.config_dir()
.join("nyaterm.redb")
.display()
.to_string(),
message: format!("failed to open store: {error}"),
ready: false,
};
format!("failed to open store: {error}"),
false,
);
}
}
}
@@ -8,7 +8,6 @@ mod security_state;
mod settings_runtime;
mod state;
pub(in crate::features) use catalog::StoreStatus;
pub(in crate::features) use security_state::{
SecurityCatalogState, SecurityFeatureFocus, SecurityFeatureState,
};
@@ -17,7 +17,7 @@ pub(in crate::features) struct SettingsFeatureState {
pub summary: AppSettingsSummary,
pub keyword_config: KeywordHighlightConfig,
pub master_password: SettingsMasterPasswordState,
pub store_status: StoreStatus,
pub(super) store_status: StoreStatus,
search_engines: SearchEngineSettingsState,
keyword_highlights: KeywordHighlightSettingsState,
appearance: AppearanceSettingsState,
@@ -39,6 +39,12 @@ pub(in crate::features) struct SettingsFeatureFocus {
pub keybindings: FocusHandle,
}
pub(in crate::features) struct StoreStatusView<'a> {
pub path: &'a str,
pub message: &'a str,
pub ready: bool,
}
struct SearchEngineSettingsState {
expanded_index: Option<usize>,
icon_picker_index: Option<usize>,
@@ -97,7 +103,9 @@ impl SettingsFeatureState {
pub(in crate::features) fn new(
summary: AppSettingsSummary,
keyword_config: KeywordHighlightConfig,
store_status: StoreStatus,
store_path: String,
store_message: String,
store_ready: bool,
ui_font_options: Vec<String>,
terminal_font_options: Vec<String>,
focus: SettingsFeatureFocus,
@@ -107,7 +115,11 @@ impl SettingsFeatureState {
summary,
keyword_config,
master_password,
store_status,
store_status: StoreStatus {
path: store_path,
message: store_message,
ready: store_ready,
},
search_engines: SearchEngineSettingsState {
expanded_index: None,
icon_picker_index: None,
@@ -139,6 +151,35 @@ impl SettingsFeatureState {
self.master_password.reset(self.summary.has_master_password);
}
pub(in crate::features) fn store_status(&self) -> StoreStatusView<'_> {
StoreStatusView {
path: &self.store_status.path,
message: &self.store_status.message,
ready: self.store_status.ready,
}
}
pub(in crate::features) fn set_store_message(&mut self, message: impl Into<String>) {
self.store_status.message = message.into();
}
pub(in crate::features) fn set_store_ready(&mut self, ready: bool) {
self.store_status.ready = ready;
}
pub(in crate::features) fn replace_store_status(
&mut self,
path: String,
message: String,
ready: bool,
) {
self.store_status = StoreStatus {
path,
message,
ready,
};
}
pub(in crate::features) fn search_engine_presentation(&self) -> SearchEnginePresentationState {
SearchEnginePresentationState {
expanded_index: self.search_engines.expanded_index,
@@ -535,7 +576,6 @@ mod tests {
};
use super::{SearchEngineMenu, SettingsFeatureFocus, SettingsFeatureState};
use crate::features::settings::StoreStatus;
use crate::models::{
ConfigPathPromptKind, KeywordHighlightEditorField, SnapshotPasswordPromptKind,
};
@@ -546,11 +586,9 @@ mod tests {
SettingsFeatureState::new(
AppSettingsSummary::default(),
KeywordHighlightConfig::default(),
StoreStatus {
path: String::new(),
message: String::new(),
ready: true,
},
String::new(),
String::new(),
true,
vec!["Inter".to_string()],
vec!["JetBrains Mono".to_string()],
SettingsFeatureFocus {
@@ -657,4 +695,26 @@ mod tests {
assert_eq!(interaction.pending_keys, None);
assert!(interaction.search_draft.is_empty());
}
#[test]
fn settings_owner_controls_store_status_updates_and_replacement() {
let mut state = settings_state();
state.set_store_message("saving settings");
state.set_store_ready(false);
let status = state.store_status();
assert_eq!(status.path, "");
assert_eq!(status.message, "saving settings");
assert!(!status.ready);
state.replace_store_status(
"/tmp/nyaterm.redb".to_string(),
"store reopened".to_string(),
true,
);
let status = state.store_status();
assert_eq!(status.path, "/tmp/nyaterm.redb");
assert_eq!(status.message, "store reopened");
assert!(status.ready);
}
}
@@ -594,15 +594,16 @@ impl NyaTermApp {
Ok(settings) => {
self.apply_gpui_settings(settings);
self.refresh_visible_terminal_surfaces(cx);
self.settings.store_status.message = "appearance settings saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("appearance settings saved".to_string());
self.settings.set_store_ready(true);
self.terminal.view.status = "appearance settings saved".to_string();
}
Err(error) => {
self.settings.store_status.message =
format!("appearance settings save failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("appearance settings save failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -107,14 +107,15 @@ impl NyaTermApp {
Ok(settings) => {
self.apply_gpui_settings(settings);
self.settings.finish_keybinding_recording();
self.settings.store_status.message = success_message.clone();
self.settings.store_status.ready = true;
self.settings.set_store_message(success_message.clone());
self.settings.set_store_ready(true);
self.terminal.view.status = success_message;
}
Err(error) => {
self.settings.store_status.message = format!("shortcut save failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("shortcut save failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -38,15 +38,16 @@ impl NyaTermApp {
{
Ok(config) => {
self.settings.keyword_config = config;
self.settings.store_status.message = "keyword highlight settings saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("keyword highlight settings saved".to_string());
self.settings.set_store_ready(true);
self.terminal.view.status = "keyword highlight settings saved".to_string();
}
Err(error) => {
self.settings.store_status.message =
format!("keyword highlight settings save failed: {error}");
self.settings.store_status.ready = false;
self.terminal.view.status = self.settings.store_status.message.clone();
self.settings
.set_store_message(format!("keyword highlight settings save failed: {error}"));
self.settings.set_store_ready(false);
self.terminal.view.status = self.settings.store_status().message.to_string();
}
}
cx.notify();
@@ -130,16 +131,18 @@ impl NyaTermApp {
self.terminal.view.status = format!(
"imported {imported_rules} keyword highlight rule(s), updated {updated_rules}, total {total_rules}"
);
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
KeywordHighlightPathPromptResult::Cancelled => {
self.terminal.view.status = "keyword highlight import cancelled".to_string();
}
KeywordHighlightPathPromptResult::Failed(error) => {
self.terminal.view.status = format!("keyword highlight import failed: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
KeywordHighlightPathPromptResult::Closed => {
self.terminal.view.status =
@@ -162,13 +162,14 @@ impl NyaTermApp {
match store.save_ui_layout_settings(&self.settings.summary) {
Ok(summary) => {
self.apply_gpui_settings(summary);
self.settings.store_status.ready = true;
self.settings.store_status.message = "panel layout saved".to_string();
self.settings.set_store_ready(true);
self.settings
.set_store_message("panel layout saved".to_string());
}
Err(error) => {
self.settings.store_status.ready = false;
self.settings.store_status.message =
format!("failed to save panel layout: {error}");
self.settings.set_store_ready(false);
self.settings
.set_store_message(format!("failed to save panel layout: {error}"));
}
}
}
@@ -44,13 +44,15 @@ impl NyaTermApp {
{
Ok(saved) => {
self.translation.settings_saved(saved);
self.settings.store_status.message = "translation settings saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("translation settings saved".to_string());
self.settings.set_store_ready(true);
}
Err(error) => {
self.translation.settings_save_failed(error);
self.settings.store_status.message = self.translation.status().to_string();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.translation.status().to_string());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -69,13 +69,15 @@ impl NyaTermApp {
self.tunnel_state.commit_tunnels(next_tunnels);
self.connection_state.close_network_move_picker();
self.terminal.view.status = format!("tunnel moved to {label}");
self.settings.store_status.message = "tunnel group saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("tunnel group saved".to_string());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to move tunnel: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -117,13 +119,15 @@ impl NyaTermApp {
self.tunnel_state.commit_proxies(next_proxies);
self.connection_state.close_network_move_picker();
self.terminal.view.status = format!("proxy moved to {label}");
self.settings.store_status.message = "proxy group saved".to_string();
self.settings.store_status.ready = true;
self.settings
.set_store_message("proxy group saved".to_string());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to move proxy: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -192,13 +196,15 @@ impl NyaTermApp {
} else {
format!("tunnel '{label}' was already deleted")
};
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = deleted;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(deleted);
}
Err(error) => {
self.terminal.view.status = format!("failed to delete tunnel: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -226,13 +232,15 @@ impl NyaTermApp {
} else {
format!("proxy '{label}' was already deleted")
};
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = deleted;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(deleted);
}
Err(error) => {
self.terminal.view.status = format!("failed to delete proxy: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -109,13 +109,15 @@ impl NyaTermApp {
self.tunnel_state.commit_tunnel_groups(groups);
self.connection_state.close_network_group_editor();
self.terminal.view.status = format!("tunnel group '{name}' saved");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to save tunnel group: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -146,13 +148,15 @@ impl NyaTermApp {
self.tunnel_state.commit_proxy_groups(groups);
self.connection_state.close_network_group_editor();
self.terminal.view.status = format!("proxy group '{name}' saved");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to save proxy group: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -220,13 +224,15 @@ impl NyaTermApp {
&deleted_tunnel_ids,
);
self.terminal.view.status = format!("tunnel group '{label}' deleted");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to delete tunnel group: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -256,13 +262,15 @@ impl NyaTermApp {
&deleted_proxy_ids,
);
self.terminal.view.status = format!("proxy group '{label}' deleted");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to delete proxy group: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
}
}
cx.notify();
@@ -199,13 +199,15 @@ impl NyaTermApp {
self.tunnel_state.commit_proxies(next_proxies);
self.connection_state.close_network_proxy_editor();
self.terminal.view.status = format!("proxy '{name}' saved");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to save proxy: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
self.connection_state
.set_network_proxy_editor_error(self.terminal.view.status.clone());
}
@@ -254,13 +254,15 @@ impl NyaTermApp {
self.tunnel_state.commit_tunnels(next_tunnels);
self.connection_state.close_network_tunnel_editor();
self.terminal.view.status = format!("tunnel '{name}' saved");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = true;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(true);
}
Err(error) => {
self.terminal.view.status = format!("failed to save tunnel: {error}");
self.settings.store_status.message = self.terminal.view.status.clone();
self.settings.store_status.ready = false;
self.settings
.set_store_message(self.terminal.view.status.clone());
self.settings.set_store_ready(false);
self.connection_state
.set_network_tunnel_editor_error(self.terminal.view.status.clone());
}
+10 -1
View File
@@ -232,7 +232,11 @@ these as staged extraction candidates, not as formatting-only refactor targets.
reconciliation, keyword expansion/edit lifecycle, appearance-menu exclusion,
keybinding recording/search transitions and kind-matched prompt completion
execute on `SettingsFeatureState`; views receive immutable presentation data
and focus handles. Filesystem prompts, persistence and GPUI notification stay
and focus handles. A storage-status follow-up made the implementation type
and backing child settings-module-private. Cross-domain persistence adapters
now update its message/readiness through owner methods, rendering receives a
borrowed immutable view, and store reopen replaces path/message/readiness as
one transition. Filesystem prompts, persistence and GPUI notification stay
in their existing adapters.
- Translation and native-update background state now have authoritative
`TranslationFeatureState` and `UpdateFeatureState` owners. Eighteen app fields
@@ -1487,6 +1491,7 @@ Current ownership map:
| Command history and persistence worker | Private state in `NyaTermApp.commands` | Persisted catalog plus background runtime | History snapshots, queue admission, event polling and idle checks enter through `CommandFeatureState`; failed optimistic use-count updates roll back on the owner. |
| Send-command composer/options/progress | Private children in `NyaTermApp.send_command` | Transient editor and send lifecycle | Views receive immutable presentation data; control edits, mutually-exclusive menus, data/mode defaults, progress counters and cancellation enter through `SendCommandFeatureState`. Session selection, terminal writes, GPUI/text-input routing and status remain in adapters. |
| Settings interaction and prompts | Private children in `NyaTermApp.settings` | Transient settings UI and prompt lifecycle | Search-engine rows, keyword-highlight editing, appearance menus, keybinding recording/search and config/diagnostics/import/password prompt admission enter through `SettingsFeatureState`; views use immutable presentation values and read-only focus/font access. Persistence, native filesystem prompts, text inputs and GPUI notification remain in adapters. |
| Global storage status | Settings-module-private child in `NyaTermApp.settings` | Runtime persistence health/presentation state | Cross-domain persistence adapters update message/readiness through `SettingsFeatureState`; rendering receives a borrowed immutable view, while store reopen replaces path/message/readiness together. Database work and compatibility handling remain in existing adapters and `nyaterm-core`. |
| AI settings/chat/history/discovery/agent/panel | AI-module-private children in `NyaTermApp.ai` | Persisted settings plus transient UI and background lifecycle | Desktop consumers use read-only slices/queries and semantic transitions; settings draft groups, menu exclusion, confirmations, request/focus preparation, detected-error throttling, picker clamping and Agent capture/reset enter through `AiFeatureState`. Persistence, terminal-context collection, GPUI focus/rendering and notification remain in adapters. |
| Shell viewport/navigation/panels/chrome/workspace | Shell-module-private children in `NyaTermApp.shell` | Transient GPUI composition and interaction state | Other desktop modules use read-only geometry/navigation/pane queries and semantic transitions; menu exclusion, settings-window lifecycle, mobile panels, failure chrome, submenu paths and pane ownership update through `ShellFeatureState`. Persistence, rendering, GPUI windows/notification and terminal coordination remain in adapters. |
| Remote Docker/process/stats panes | Private children in `NyaTermApp.remote_ops` | Transient UI state plus typed background-event lifecycle | Views use immutable presentation values; menu exclusion, list-offset clamping, Docker details/Compose/confirmation cleanup, process PID-scoped cleanup, Stats expansion/data and job identity/failure timing enter through `RemoteOpsFeatureState`. SSH service launch, active-session policy, terminal status mirroring and GPUI notification remain in adapters. |
@@ -1746,6 +1751,10 @@ honest remaining list.
cross-domain consumers receive a borrowed immutable view, and navigation,
session cache, history/favorites, search/sort, path editing, selection/rename,
menu and resize transitions moved onto `TransferFeatureState`.
The settings storage-status batch then made the backing child and
implementation type settings-module-private, moved cross-domain status
writes behind owner transitions, and gave rendering a borrowed immutable
view without changing storage execution or compatibility formats.
What remains at the
composition root is stores, runtime and focused feature owners.
Group by cohesion where a cluster exists; do not force the count down for
+17
View File
@@ -251,6 +251,23 @@ check_no_multiline_matches \
"settings interaction child access must use SettingsFeatureState methods" \
'(self|this|app)\.settings[[:space:]]*\.(search_engines|keyword_highlights|appearance|keybindings|prompts)(\.|[[:space:]]*=)' \
crates/nyaterm-desktop/src/features
check_no_multiline_matches \
"SettingsFeatureState store status must stay inside the settings module" \
'struct[[:space:]]+SettingsFeatureState[[:space:]]*\{[^}]*pub([[:space:]]|\(crate\)|\(in crate\)|\(in crate::features\))[[:space:]]+store_status[[:space:]]*:' \
crates/nyaterm-desktop/src/features/settings/state.rs
check_no_matches \
"StoreStatus implementation must stay inside the settings module" \
'pub([[:space:]]|\(crate\)|\(in crate\)|\(in crate::features\))[[:space:]]+struct[[:space:]]+StoreStatus' \
crates/nyaterm-desktop/src/features/settings/catalog.rs
check_no_matches \
"StoreStatus implementation must not be re-exported from settings" \
'pub([^;]*)use([^;]*)StoreStatus' \
crates/nyaterm-desktop/src/features/settings/mod.rs
check_no_multiline_matches_excluding \
"settings store status access must use SettingsFeatureState methods outside settings" \
'(self|this|app)\.settings[[:space:]]*\.[[:space:]]*store_status[[:space:]]*(\.|=)' \
crates/nyaterm-desktop/src/features \
'crates/nyaterm-desktop/src/features/settings/**'
check_no_matches \
"translation state must stay grouped under TranslationFeatureState" \