Build 18: move the integrated node off the wallet list into the cog

- Remove the Node status chip from the wallet-list header; the integrated
  node now lives in the cog's settings as a status + Enable/Autorun section,
  with a "Node settings" button into the full stats/mining/tuning panel.
- Stop the desktop two-column rail from auto-docking the node beside the
  settings screen, so the node has a single home in the cog at every width
  (the full panel opens only on demand). Fixes the wide-window double-exposure.
- Prove NIP-17 payments transit the top public relays: parameterize the
  nostr e2e roundtrip and add damus.io + nos.lol proofs (3/3 green).
- Add DEVELOPING.md documenting how we iterate and test (Xwayland :2 recipe,
  Build N cadence, gui-sweep, e2e tests, live infra) — no secrets.

Verified live on :2: chip gone from the list; cog shows the node once
(single column, no left rail); "Node settings" opens the full panel on
demand. 34 lib tests green; nip17 roundtrip green over nrelay + damus + nos.lol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-06-11 20:21:13 -04:00
parent 86f042facb
commit 0438d70cae
5 changed files with 293 additions and 48 deletions
+6 -1
View File
@@ -102,8 +102,13 @@ impl ContentContainer for Content {
// the panel opens only when explicitly toggled, never forced open by
// dual-panel mode (otherwise GRIM's node column dominates the list).
let list_screen = self.wallets.wallet_list_screen();
// The app-settings (cog) screen owns the node now: it lives in the
// cog's own section, and the full panel opens only when explicitly
// requested from there — never auto-docked beside settings, which
// would expose the node twice on a wide screen.
let app_settings = self.wallets.showing_settings();
let show_network = !wallet_open
&& if list_screen {
&& if list_screen || app_settings {
Self::is_network_panel_open()
} else {
is_panel_open
+49 -2
View File
@@ -14,11 +14,13 @@
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::GLOBE_SIMPLE;
use crate::gui::icons::{DATABASE, FADERS, GLOBE_SIMPLE, POWER};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::network::NetworkContent;
use crate::gui::views::settings::{InterfaceSettingsContent, NetworkSettingsContent};
use crate::gui::views::types::ContentContainer;
use crate::gui::views::{Content, View};
use crate::node::Node;
/// Application settings content.
pub struct SettingsContent {
@@ -64,6 +66,51 @@ impl SettingsContent {
self.network_settings.ui(ui, cb);
ui.add_space(8.0);
// Integrated node — relocated here from the wallet-list chip so the
// list stays uncluttered. Quick status + enable/autorun, plus a button
// into the full node panel (stats, mining, tuning, recovery).
View::horizontal_line(ui, Colors::stroke());
ui.add_space(6.0);
View::sub_title(ui, format!("{} {}", DATABASE, t!("network.node")));
View::horizontal_line(ui, Colors::stroke());
ui.add_space(8.0);
let running = Node::is_running();
let (status_color, status_text) = if !running {
(Colors::gray(), "Disabled")
} else if Node::not_syncing() {
(Colors::pos(), "Running · synced")
} else {
(Colors::gold(), "Running · syncing…")
};
ui.vertical_centered(|ui| {
ui.label(
egui::RichText::new(status_text)
.size(15.0)
.color(status_color),
);
});
ui.add_space(8.0);
if !running {
View::action_button(
ui,
format!("{} {}", POWER, t!("network.enable_node")),
|| {
Node::start();
},
);
ui.add_space(4.0);
}
NetworkContent::autorun_node_ui(ui);
ui.add_space(8.0);
View::action_button(ui, format!("{} {}", FADERS, t!("network.settings")), || {
if !Content::is_network_panel_open() {
Content::toggle_network_panel();
}
});
ui.add_space(8.0);
// Do not show Tor settings on Android.
// let os = OperatingSystem::from_target_os();
// let show_tor = os != OperatingSystem::Android;
+3 -40
View File
@@ -574,49 +574,12 @@ impl WalletsContent {
}
}
/// Slim header for the wallet-list surface: a tappable integrated-node
/// status chip (opens the full node panel — every feature intact, just
/// opt-in) on the left, and the app-settings gear on the right.
/// Slim header for the wallet-list surface: just the app-settings gear,
/// right-aligned. The integrated node moved into the gear's settings — out
/// of the list, every node feature still intact.
fn wallet_list_header_ui(&mut self, ui: &mut egui::Ui) {
use crate::node::Node;
ui.add_space(6.0);
ui.horizontal(|ui| {
// Node status chip.
let running = Node::is_running();
let synced = running && Node::not_syncing();
let (dot, label) = if !running {
(Colors::gray(), "Node")
} else if synced {
(Colors::pos(), "Node synced")
} else {
(Colors::gold(), "Syncing…")
};
let galley = ui.painter().layout_no_wrap(
label.to_string(),
eframe::epaint::FontId::proportional(14.0),
Colors::text(false),
);
let pad = egui::Vec2::new(12.0, 7.0);
let chip_w = 10.0 + 8.0 + galley.size().x + pad.x * 2.0;
let (rect, resp) =
ui.allocate_exact_size(egui::Vec2::new(chip_w, 34.0), Sense::click());
ui.painter()
.rect_filled(rect, CornerRadius::same(17), Colors::fill_lite());
let dot_c = egui::pos2(rect.min.x + pad.x + 5.0, rect.center().y);
ui.painter().circle_filled(dot_c, 4.0, dot);
ui.painter().galley(
egui::pos2(dot_c.x + 13.0, rect.center().y - galley.size().y / 2.0),
galley,
Colors::text(false),
);
if resp
.on_hover_cursor(CursorIcon::PointingHand)
.on_hover_text("Node settings")
.clicked()
{
Content::toggle_network_panel();
}
// App-settings gear, right-aligned. Drawn manually (the title-bar
// button uses dark-on-yellow ink, invisible on this dark surface).
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {