fix: TUI shows ETH address, /peer 0x... resolves, Cmd+key on macOS

TUI header: shows ETH address (0x...) instead of fingerprint
/peer 0x...: resolves via GET /v1/resolve/:address endpoint
Cmd+A/E/U/K/W: macOS SUPER modifier now handled alongside CONTROL
Added resolve_address() method for ETH/any address resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 08:20:38 +04:00
parent 3efce2ddf4
commit 2aa58a4319
4 changed files with 55 additions and 10 deletions

View File

@@ -379,6 +379,12 @@ impl App {
Some(resolved) => resolved,
None => return,
}
} else if raw.starts_with("0x") || raw.starts_with("0X") {
// Resolve ETH address via server
match self.resolve_address(&raw, client).await {
Some(resolved) => resolved,
None => return,
}
} else {
raw
};
@@ -928,6 +934,29 @@ impl App {
}
}
/// Resolve an ETH address (0x...) or any address format via the server.
pub(crate) async fn resolve_address(&self, addr: &str, client: &ServerClient) -> Option<String> {
let url = format!("{}/v1/resolve/{}", client.base_url, addr);
match client.client.get(&url).send().await {
Ok(resp) => {
if let Ok(data) = resp.json::<serde_json::Value>().await {
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
self.add_message(ChatLine { sender: "system".into(), text: format!("{} → {}", addr, &fp[..fp.len().min(16)]), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
return Some(fp.to_string());
}
if let Some(err) = data.get("error") {
self.add_message(ChatLine { sender: "system".into(), text: format!("Cannot resolve {}: {}", addr, err), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
}
}
None
}
Err(e) => {
self.add_message(ChatLine { sender: "system".into(), text: format!("Error: {}", e), is_system: true, is_self: false, message_id: None, timestamp: Local::now() });
None
}
}
}
async fn list_aliases(&self, client: &ServerClient) {
let url = format!("{}/v1/alias/list", client.base_url);
match client.client.get(&url).send().await {

View File

@@ -58,9 +58,14 @@ impl App {
} else {
(" \u{25CF}", Color::Red) // ●
};
let identity_display = if self.our_eth.is_empty() {
self.our_fp.clone()
} else {
format!("{}", &self.our_eth[..self.our_eth.len().min(12)])
};
let header = Paragraph::new(Line::from(vec![
Span::styled("WZ ", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
Span::styled(&self.our_fp, Style::default().fg(Color::Green)),
Span::styled(identity_display, Style::default().fg(Color::Green)),
Span::raw(" \u{2192} "),
Span::styled(peer_str, Style::default().fg(Color::Yellow)),
Span::styled(

View File

@@ -51,9 +51,9 @@ impl App {
self.cursor_pos += 1;
}
}
// Home / Ctrl+A
// Home / Ctrl+A / Cmd+A (macOS)
KeyCode::Home => { self.cursor_pos = 0; }
KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SUPER) => {
self.cursor_pos = 0;
}
// End: cursor to end of input when typing, snap to bottom when input is empty.
@@ -70,20 +70,20 @@ impl App {
self.cursor_pos = self.input.len();
}
}
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SUPER) => {
self.cursor_pos = self.input.len();
}
// Ctrl+U: clear line
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Ctrl+U / Cmd+U: clear line
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SUPER) => {
self.input.clear();
self.cursor_pos = 0;
}
// Ctrl+K: kill to end of line
KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Ctrl+K / Cmd+K: kill to end of line
KeyCode::Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SUPER) => {
self.input.truncate(self.cursor_pos);
}
// Ctrl+W: delete word back
KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) => {
// Ctrl+W / Cmd+W: delete word back
KeyCode::Char('w') if key.modifiers.contains(KeyModifiers::CONTROL) || key.modifiers.contains(KeyModifiers::SUPER) => {
let before = &self.input[..self.cursor_pos];
let new_pos = before.trim_end().rfind(' ').map(|i| i + 1).unwrap_or(0);
self.input.drain(new_pos..self.cursor_pos);

View File

@@ -41,6 +41,8 @@ pub struct App {
pub receipts: Arc<Mutex<HashMap<String, ReceiptStatus>>>,
/// Pending incoming file transfers, keyed by file ID.
pub pending_files: Arc<Mutex<HashMap<String, PendingFileTransfer>>>,
/// Our ETH address (derived from seed).
pub our_eth: String,
/// Scroll offset from bottom (0 = pinned to newest).
pub scroll_offset: usize,
/// Whether the WebSocket connection is active.
@@ -99,6 +101,14 @@ impl App {
timestamp: Local::now(),
});
// Derive ETH address from seed if available
let our_eth = crate::keystore::load_seed_raw()
.map(|seed| {
let eth = warzone_protocol::ethereum::derive_eth_identity(&seed);
eth.address.to_checksum()
})
.unwrap_or_default();
App {
input: String::new(),
messages,
@@ -110,6 +120,7 @@ impl App {
cursor_pos: 0,
receipts: Arc::new(Mutex::new(HashMap::new())),
pending_files: Arc::new(Mutex::new(HashMap::new())),
our_eth,
scroll_offset: 0,
connected: Arc::new(AtomicBool::new(false)),
}