refactor(notice): make the notice table exhaustive and drop dead wire entries

D5 cleanup, frontend and backend in one commit: removes the six
config_validate::* statuses that never receive a backend send and the
set_config::ok send (enum variants, golden list 41→34, send point in
restart_clash_core), their frontend handlers, and the five i18n keys only
those handlers used. The notice table is now typed as a Record over the
generated NoticeStatus union, so a status without a frontend entry fails
to compile. The info status (sent by the silent updater for breaking
change notices, previously dropped with a console warning) now shows an
info toast — the one deliberate behavior delta.
This commit is contained in:
Tunglies
2026-10-01 04:20:26 +08:00
parent 139cc09304
commit 9e7669ff50
21 changed files with 42 additions and 180 deletions
-8
View File
@@ -24,14 +24,6 @@ These rules supplement the repository's root `AGENTS.md`.
status strings pinned by the golden tests. Status additions or changes
require frontend and backend changes in the same PR and an updated golden
list; this protects the wire contract. No exceptions.
Known dead wire entries, recorded here and intentionally kept (no tracking
issue): the `config_validate::*` statuses whose frontend handlers never
receive a backend send (`core_change`, `stdout_error`, `yaml_key_error`,
`yaml_error`, `merge_key_error`, `merge_error`), and `set_config::ok`,
sent by the backend with no frontend handler. Each variant carries its own
comment in `NoticeStatus`. Removal requires a same-PR frontend change and a
golden-list update.
5. **Preserve lock order.** The order is `config_write` before
`config_update_in_progress` before `lifecycle_lock`. Patch callers must use
the executor's guarded claim instead of independently combining these locks;
+1 -43
View File
@@ -7,7 +7,6 @@ pub use notification::frontend_wire_contract;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum NoticeStatus {
// dead send: no frontend handler
#[serde(rename = "info")]
Info,
#[serde(rename = "import_sub_url::ok")]
@@ -78,40 +77,13 @@ pub enum NoticeStatus {
MixedPortFallback,
#[serde(rename = "mixed_port::fallback_error")]
MixedPortFallbackError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::core_change")]
ConfigValidateCoreChange,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::stdout_error")]
ConfigValidateStdoutError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::yaml_key_error")]
ConfigValidateYamlKeyError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::yaml_error")]
ConfigValidateYamlError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::merge_key_error")]
ConfigValidateMergeKeyError,
// dead listener: frontend handler retained
#[allow(dead_code)]
#[serde(rename = "config_validate::merge_error")]
ConfigValidateMergeError,
// dead send: no frontend handler
#[serde(rename = "set_config::ok")]
SetConfigOk,
}
impl NoticeStatus {
/// Every variant in golden order. The wire-contract export and the golden
/// test both derive their variant list from here; expected strings stay
/// hand-written in the test so renames cannot slip through.
pub const ALL: [Self; 41] = [
pub const ALL: [Self; 34] = [
Self::Info,
Self::ImportSubUrlOk,
Self::ImportSubUrlError,
@@ -146,13 +118,6 @@ impl NoticeStatus {
Self::ConfigCoreChangeError,
Self::MixedPortFallback,
Self::MixedPortFallbackError,
Self::ConfigValidateCoreChange,
Self::ConfigValidateStdoutError,
Self::ConfigValidateYamlKeyError,
Self::ConfigValidateYamlError,
Self::ConfigValidateMergeKeyError,
Self::ConfigValidateMergeError,
Self::SetConfigOk,
];
}
@@ -198,13 +163,6 @@ mod tests {
"config_core::change_error",
"mixed_port::fallback",
"mixed_port::fallback_error",
"config_validate::core_change",
"config_validate::stdout_error",
"config_validate::yaml_key_error",
"config_validate::yaml_error",
"config_validate::merge_key_error",
"config_validate::merge_error",
"set_config::ok",
];
let actual: Vec<_> = statuses
.into_iter()
+4 -3
View File
@@ -355,11 +355,11 @@ fn sample_frontend_events(uid: &String) -> Vec<(FrontendEvent<'_>, &'static str,
),
(
FrontendEvent::NoticeMessage {
status: NoticeStatus::SetConfigOk,
status: NoticeStatus::Info,
message: Arc::from("ok"),
},
"verge://notice-message",
json!(["set_config::ok", "ok"]),
json!(["info", "ok"]),
),
(
FrontendEvent::TimerUpdated { profile_index: uid },
@@ -414,7 +414,8 @@ pub fn frontend_wire_contract() -> serde_json::Value {
.collect();
let statuses: Vec<_> = NoticeStatus::ALL
.iter()
.map(|status| serde_json::to_value(status).expect("NoticeStatus serializes"))
// A unit variant with a string rename cannot fail to serialize.
.filter_map(|status| serde_json::to_value(status).ok())
.collect();
json!({ "version": 1, "events": events, "noticeStatuses": statuses })
}
-1
View File
@@ -29,7 +29,6 @@ pub async fn restart_clash_core() {
match CoreManager::global().restart_core().await {
Ok(_) => {
announce(Refresh::Clash);
handle::Handle::notice(NoticeStatus::SetConfigOk, "ok");
}
Err(err) => {
handle::Handle::notice(NoticeStatus::SetConfigError, format!("{err:#}"));