Skip to content

Add a Settings view#21

Merged
pool2win merged 8 commits into
p2poolv2:mainfrom
framicheli:settings-view
Apr 23, 2026
Merged

Add a Settings view#21
pool2win merged 8 commits into
p2poolv2:mainfrom
framicheli:settings-view

Conversation

@framicheli
Copy link
Copy Markdown
Contributor

@framicheli framicheli commented Apr 17, 2026

Add a Settings View where the user can set:
Bitcoin Config file Path,
LN Config,
P2PoolV2 Config file path

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 94.12781% with 68 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/main.rs 86.42% 52 Missing ⚠️
src/components/bitcoin_config_view.rs 94.25% 5 Missing ⚠️
src/components/settings_view.rs 97.98% 4 Missing ⚠️
src/settings.rs 98.13% 4 Missing ⚠️
src/components/file_explorer.rs 97.11% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Settings screen to the TUI and introduces persisted user settings (TOML on disk) so config file paths can be remembered across runs.

Changes:

  • Introduce SettingsView UI + sidebar item and route rendering/input handling for the new screen.
  • Add settings module for loading/saving settings.toml, and bootstrap app state from persisted paths at startup.
  • Update status bar hints and snapshot/UI tests to cover the new sidebar item and Settings screen rendering.

Reviewed changes

Copilot reviewed 37 out of 38 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/snapshots/ui_snapshots__home_screen_render.snap Snapshot update to include the new “Settings” sidebar item.
tests/snapshots/ui_snapshots__config_screen_render.snap Snapshot update to include the new “Settings” sidebar item.
src/ui.rs Render routing updated to include CurrentScreen::Settings + new snapshot test.
src/main.rs Loads settings on startup, adds Settings input handling, persists selected paths, expands action handling/tests.
src/settings.rs New persisted settings module (Settings struct + TOML load/save + tests).
src/lib.rs Exposes new settings module publicly.
src/app.rs Adds CurrentScreen::Settings, ExplorerTrigger, and stores settings/settings_view in App.
src/components/status_bar.rs Adds Settings-specific status bar hints and error display.
src/components/settings_view.rs New Settings screen component: field list rendering + key handling.
src/components/file_explorer.rs Minor rendering/title formatting changes + render test.
src/components/bitcoin_config_view.rs Path shortening refactor + minor render correctness improvements + extra render test.
src/components/bitcoin_status_view.rs Adds compile-time assertion for tab count + #[must_use] on constructor.
src/components/home_view.rs Adds #[must_use] on constructor.
src/components/ln_config_view.rs Adds #[must_use] on constructor.
src/components/ln_status_view.rs Adds #[must_use] on constructor.
src/components/p2pool_config_view.rs Adds #[must_use] on constructor.
src/components/p2pool_status_view.rs Adds #[must_use] on constructor.
src/components/shares_market_view.rs Adds #[must_use] on constructor; fixes stale comment label.
src/components/mod.rs Exports settings_view module.
src/bitcoin_config.rs Adds docs/annotations and refactors parsing fallbacks; adds test for malformed INI fallback behavior.
src/snapshots/pdm__ui__tests__shares_market_screen_render.snap Snapshot update for new sidebar item.
src/snapshots/pdm__ui__tests__settings_screen_render.snap New snapshot for Settings screen rendering.
src/snapshots/pdm__ui__tests__p2pool_status_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__ui__tests__p2pool_screen_render.snap New snapshot for P2Pool screen rendering (with updated sidebar).
src/snapshots/pdm__ui__tests__p2pool_config_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__ui__tests__ln_status_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__ui__tests__ln_config_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__ui__tests__home_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__ui__tests__bitcoin_status_tab_system_render.snap Snapshot update for new sidebar item.
src/snapshots/pdm__ui__tests__bitcoin_status_tab_peers_render.snap Snapshot update for new sidebar item.
src/snapshots/pdm__ui__tests__bitcoin_status_tab_logs_render.snap Snapshot update for new sidebar item.
src/snapshots/pdm__ui__tests__bitcoin_status_screen_render.snap Snapshot update for new sidebar item.
src/snapshots/pdm__ui__tests__bitcoin_screen_render.snap New snapshot for Bitcoin Config screen rendering (with updated sidebar).
src/snapshots/pdm__ui__tests__bitcoin_config_screen_render.snap Snapshot update for new sidebar item + assertion line metadata.
src/snapshots/pdm__tests__menu_toggled.snap Snapshot update for new sidebar item.
src/snapshots/pdm__tests__home_screen.snap Snapshot update for new sidebar item.
Cargo.toml Adds directories, serde, toml, serial_test; bumps ratatui to 0.30.0.
Cargo.lock Lockfile updates reflecting new deps and the ratatui upgrade.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/components/settings_view.rs Outdated
Comment on lines +73 to +77
if FIELDS[self.selected_index].1 == FieldKind::FilePicker {
AppAction::OpenExplorerForSettings(self.selected_index)
} else {
AppAction::None
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DirectoryInput path (e.g. “Settings directory override”) is currently unconfigurable: on KeyCode::Enter, only FilePicker fields open the explorer and all other fields return AppAction::None, and there’s no text-entry mode. Either implement an input flow for DirectoryInput (editable text field / directory picker) or remove/disable this field to avoid presenting an option that can’t be set.

Copilot uses AI. Check for mistakes.
Comment thread src/main.rs Outdated
Comment on lines +267 to +274
match field {
0 => app.settings.bitcoin_conf_path = Some(path.clone()),
1 => app.settings.p2pool_conf_path = Some(path.clone()),
2 => app.settings.ln_conf_path = Some(path.clone()),
3 => app.settings.shares_market_conf_path = Some(path.clone()),
_ => {}
}
if let Err(e) = save_settings(&app.settings) {
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExplorerTrigger::Settings(field) only updates app.settings.* and saves it. It does not update the active in-memory state used by other screens (e.g., app.bitcoin_conf_path/app.bitcoin_data, app.p2pool_conf_path/app.p2pool_config), so changes made in Settings won’t take effect until restart (or re-selecting in the other screen). Consider reusing the same validation/loading logic here so Settings changes apply immediately.

Suggested change
match field {
0 => app.settings.bitcoin_conf_path = Some(path.clone()),
1 => app.settings.p2pool_conf_path = Some(path.clone()),
2 => app.settings.ln_conf_path = Some(path.clone()),
3 => app.settings.shares_market_conf_path = Some(path.clone()),
_ => {}
}
if let Err(e) = save_settings(&app.settings) {
let mut should_save_settings = true;
match field {
0 => match parse_bitcoin_config(&path) {
Ok(entries) => {
const MIN_KNOWN_KEYS: usize = 1;
let known_key_count = entries
.iter()
.filter(|e| e.enabled && e.schema.is_some())
.count();
if known_key_count >= MIN_KNOWN_KEYS {
app.bitcoin_conf_path = Some(path.clone());
app.bitcoin_data = entries;
app.bitcoin_config_view.selected_index = 0;
app.bitcoin_config_view.dirty = false;
app.bitcoin_config_view.sidebar_focused = false;
app.bitcoin_config_view.warning_message = None;
app.settings.bitcoin_conf_path = Some(path.clone());
app.settings_view.save_error = None;
} else {
should_save_settings = false;
app.settings_view.save_error = Some(
"Selected file does not appear to be a Bitcoin config."
.to_string(),
);
}
}
Err(e) => {
should_save_settings = false;
app.settings_view.save_error = Some(format!(
"Failed to read Bitcoin config: {e}. Check permissions and try again."
));
}
},
1 => {
app.p2pool_conf_path = Some(path.clone());
if let Some(p) = path.to_str()
&& let Ok(cfg) = P2PoolConfig::load(p)
{
app.p2pool_config = Some(cfg);
}
app.settings.p2pool_conf_path = Some(path.clone());
app.settings_view.save_error = None;
}
2 => {
app.settings.ln_conf_path = Some(path.clone());
app.settings_view.save_error = None;
}
3 => {
app.settings.shares_market_conf_path = Some(path.clone());
app.settings_view.save_error = None;
}
_ => {}
}
if should_save_settings
&& let Err(e) = save_settings(&app.settings)
{

Copilot uses AI. Check for mistakes.
Comment thread src/main.rs
Comment on lines +246 to +250
app.settings.bitcoin_conf_path = Some(path.clone());
if let Err(e) = save_settings(&app.settings) {
app.settings_view.save_error =
Some(format!("Save failed: {e}"));
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save_error is only set on failure here, but it’s never cleared on a subsequent successful save_settings call. If a save fails once, the error will keep showing in the Settings status bar even after later saves succeed. Consider clearing app.settings_view.save_error before attempting the save, or explicitly clearing it on success (and apply the same pattern to the other save_settings call sites).

Copilot uses AI. Check for mistakes.
Comment thread src/main.rs Outdated
Comment on lines +307 to +308
0 => app.settings.bitcoin_conf_path = None,
1 => app.settings.p2pool_conf_path = None,
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClearSettingsField clears the persisted app.settings.* value but doesn’t clear the corresponding active runtime state (e.g., app.bitcoin_conf_path/app.bitcoin_data, app.p2pool_conf_path/app.p2pool_config). This can leave Settings showing “(not set)” while other screens still operate on the previously loaded paths. Consider also clearing/invalidating the related app.* fields here (or explicitly documenting that it only affects the next restart).

Suggested change
0 => app.settings.bitcoin_conf_path = None,
1 => app.settings.p2pool_conf_path = None,
0 => {
app.settings.bitcoin_conf_path = None;
app.bitcoin_conf_path = None;
app.bitcoin_data.clear();
}
1 => {
app.settings.p2pool_conf_path = None;
app.p2pool_conf_path = None;
app.p2pool_config = P2PoolConfig::default();
}

Copilot uses AI. Check for mistakes.
Comment thread src/main.rs Outdated
Comment on lines +248 to +249
app.settings_view.save_error =
Some(format!("Save failed: {e}"));
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On save_settings failure during Bitcoin config selection, the error is stored in app.settings_view.save_error, but the status bar only displays that value on CurrentScreen::Settings. As a result, the user gets no feedback that persistence failed unless they navigate to Settings. Consider surfacing the error on the current screen (e.g., via bitcoin_config_view.warning_message / a generic status bar error), or logging it explicitly.

Suggested change
app.settings_view.save_error =
Some(format!("Save failed: {e}"));
let save_error = format!("Save failed: {e}");
app.settings_view.save_error = Some(save_error.clone());
app.bitcoin_config_view.warning_message = Some(save_error);

Copilot uses AI. Check for mistakes.
Comment thread src/settings.rs Outdated
Comment on lines +162 to +164
fn save_settings_public_fn_creates_file() {
let dir = tempfile::tempdir().unwrap();
// Point save_settings at a path we control by writing directly
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other settings tests: this one doesn’t call save_settings() at all (it serializes and writes the file directly), so it isn’t verifying that save_settings() creates the config directory and writes settings.toml. Suggest either rewriting it to call save_settings() (with PDM_CONFIG_DIR redirected) or renaming the test to avoid implying coverage it doesn’t provide.

Suggested change
fn save_settings_public_fn_creates_file() {
let dir = tempfile::tempdir().unwrap();
// Point save_settings at a path we control by writing directly
fn serialized_settings_can_be_written_to_file() {
let dir = tempfile::tempdir().unwrap();
// Write the serialized settings directly to a controlled path.

Copilot uses AI. Check for mistakes.
Comment thread src/settings.rs Outdated
Comment on lines +29 to +31
/// If set, the user has chosen a custom settings directory.
/// Takes effect on restart.
pub settings_dir_override: Option<PathBuf>,
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settings_dir_override is documented as “takes effect on restart”, but nothing in this module consults it when resolving where settings.toml lives (see config_dir() below). As implemented, the override can never actually take effect even if the value were set. Consider either removing this field for now, or implementing the restart behavior by incorporating the override into config_dir() resolution with a safe fallback.

Copilot uses AI. Check for mistakes.
Comment thread src/settings.rs Outdated
Comment on lines +133 to +137
fn save_and_load_via_public_functions() {
// Use a temp dir as the config dir by writing directly then calling load_settings
// via the file path rather than the env-var route (avoids unsafe set_var in 2024 edition)
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("settings.toml");
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is named as if it exercises the public save_settings/load_settings functions, but the body only writes/reads TOML directly and never calls those functions. That won’t catch regressions in config_dir() resolution, directory creation, or the error-handling behavior of load_settings(). Consider rewriting the test to call save_settings() + load_settings() (using PDM_CONFIG_DIR + #[serial] if needed), or renaming it to reflect what it actually tests.

Copilot uses AI. Check for mistakes.
@pool2win pool2win self-assigned this Apr 23, 2026
Copy link
Copy Markdown
Contributor

@pool2win pool2win left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one. I was able to change the settings and save them in the settings location. On restart everything was there.

@pool2win pool2win merged commit 27d135c into p2poolv2:main Apr 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants