fix(doctor): name the config keys tty7 does not read

Mistyping a setting name is the likeliest thing to go wrong in a
hand-edited `config.json`, and the quietest. The file still parses, so

    config           ok

while the setting does nothing. `note_unknown_keys` has found these all
along — it says so with `log::warn!` behind a `log_enabled!` guard, and
per `docs/reference/privacy.mdx` there is no log at all unless `TTY7_LOG`
is set. Measured on a config carrying `font_siz`, `scrollback_limitt` and
one real key: nothing anywhere named the two that did nothing.

Unlike the keybindings map, this one `doctor` can ask: `unknown_keys` is
already in the crate the CLI shares, and already guarded by a test that
no real field may ever be reported as a typo. It only needed a way in.

    config keys      not settings tty7 reads, so they do nothing:
                     font_siz, scrollback_limitt — check the spelling
                     against the reference page

Only when the config parsed, and only when there is something to say. A
quarantined config is running on defaults and *every* key in it is
unread; naming them all would bury the row that matters. A clean config
prints no row at all, so this reads as news.

The test asks the pure function rather than the file-reading wrapper.
`unknown_config_keys` reads `TTY7_CONFIG_DIR`, and setting that from a
test steers every other test in the process — the first draft did, and
failed three runs out of three under the parallel suite.
This commit is contained in:
l0ng-ai
2026-08-23 00:35:46 +08:00
parent fa4a7e7e38
commit 1552abb010
2 changed files with 78 additions and 0 deletions
+22
View File
@@ -1806,6 +1806,28 @@ fn doctor(ctx: &Context, backend: &mut dyn Backend) -> Result<Outcome> {
vec![address::ENV_PANE.to_string(), mark(&ctx.pane)],
vec!["config".to_string(), config_state.clone()],
];
// A key tty7 does not read is the likeliest thing to go wrong in a
// hand-edited config, and the quietest: the file parses, so the row above
// says `ok`, and the setting simply does nothing. `note_unknown_keys`
// already finds these — it just says so into a log that is not written
// unless `TTY7_LOG` is set.
//
// Only when the config parsed. A quarantined one is running on defaults
// and every key in it is unread; naming them all would bury the row that
// matters.
let unread_keys = config_ok
.then(tty7_core::core::config::unknown_config_keys)
.filter(|keys| !keys.is_empty());
if let Some(keys) = &unread_keys {
rows.push(vec![
"config keys".to_string(),
format!(
"not settings tty7 reads, so they do nothing: {} — check the spelling against \
the reference page",
keys.join(", ")
),
]);
}
if let Some(kept) = &quarantined_tree {
let newest = kept
.last()
+56
View File
@@ -1136,6 +1136,29 @@ fn note_unknown_keys(text: &str) {
}
}
/// The keys in the user's `config.json` that tty7 does not read, or an empty
/// list when the file is absent or does not parse.
///
/// [`note_unknown_keys`] already finds these and says so with `log::warn!`,
/// behind a `log_enabled!` guard — and per `docs/reference/privacy.mdx` there
/// is no log at all unless `TTY7_LOG` is set. Mistyping a setting name is the
/// likeliest thing to go wrong in a hand-edited config, and on a default
/// install it is answered with silence: the file parses, so `doctor` reports
/// the config `ok` while the setting does nothing.
///
/// A file that does not parse answers empty on purpose. It is already
/// quarantined and reported as such, and every key in it is unread — naming
/// them all would bury the one thing worth saying.
pub fn unknown_config_keys() -> Vec<String> {
let Some(path) = config_path("config.json") else {
return Vec::new();
};
let Ok(text) = std::fs::read_to_string(&path) else {
return Vec::new();
};
unknown_keys(strip_bom(&text))
}
/// The comparison behind [`note_unknown_keys`], separated so the property that
/// matters can be tested: no real field may ever be named here.
fn unknown_keys(text: &str) -> Vec<String> {
@@ -1682,6 +1705,39 @@ mod tests {
assert!(!back.dim_inactive_panes);
}
/// What a reader can be told about a hand-edited config.
///
/// `note_unknown_keys` has found these all along and said so with
/// `log::warn!` behind a `log_enabled!` guard, so on a default install —
/// where no log is written at all — mistyping a setting name is answered
/// with silence: the file parses, `doctor` calls the config `ok`, and the
/// setting does nothing. This is the question `doctor` asks instead.
///
/// Asked of the text rather than through `unknown_config_keys`, which
/// reads `TTY7_CONFIG_DIR`: setting that from a test steers every other
/// test in the process, and the suite runs them in parallel. The wrapper
/// adds only "no file is not a typo" to what is checked here.
#[test]
fn the_keys_a_config_file_wastes_can_be_named() {
assert!(
unknown_keys(r#"{"font_size": 13.0}"#).is_empty(),
"a real setting is not a typo"
);
assert_eq!(
unknown_keys(r#"{"font_siz": 13.0, "font_size": 13.0}"#),
vec!["font_siz".to_string()],
"the typo is named and its correct neighbour is not"
);
assert!(
unknown_keys(r#"{"font_size": "#).is_empty(),
"a file that does not parse is the quarantine's news, not this one's"
);
assert!(
unknown_keys("").is_empty(),
"and nothing at all says nothing"
);
}
/// Every field the struct has must be recognised, and only those.
///
/// The known set comes from serialising the default, so this is really