feat(ui): mobile mesh tabs, AIUI-style audio player, cloud grid + map fixes
UI (this session): - Global audio player now scales the whole interface into the space above it on desktop (sidebar + main) and docks directly above the tab bar on mobile; it stays visible while navigating. - Mesh mobile redesign: floating Chat / BTC / Dead Man / AI / Map tab strip with a single fixed, internally-scrolling pane (page no longer scrolls); tabs hide while a conversation is open; floating back button; collapsible Device panel (starts collapsed); keyboard-aware conversation sizing via VisualViewport so the chat sits just above the keyboard. - Cloud file grid: uniform 4/3 card heights (folders + images match). - Swipe left/right switches tabs on the Apps and Web5 screens. - Map tool fills its pane (no bottom gap); fix skewed Share Location toggle on mobile (global min-height rule was deforming the switch). - Trim redundant helper copy from the mesh AI tab. Also bundles pre-existing in-progress work that was already in the tree: mesh listener/session + wallet + container + bitcoin-status backend changes, docker UI updates, and assorted other UI tweaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c4855526fe
commit
1bce694ebb
@@ -22,11 +22,23 @@ use tracing::{debug, warn};
|
||||
const CACHE_REFRESH_SECS: u64 = 5;
|
||||
const CACHE_ERROR_BACKOFF_SECS: u64 = 5;
|
||||
|
||||
// Grace window before a failing poll marks the snapshot "stale" for the UI.
|
||||
// On a busy / swap-thrashing node (e.g. .198) getblockchaininfo intermittently
|
||||
// exceeds the RPC timeout, so a single missed poll is normal and must NOT flip
|
||||
// the UI to "reconnecting…". Only after the cached snapshot is genuinely old —
|
||||
// several polls failed in a row — do we surface the banner.
|
||||
const STALE_GRACE_MS: u64 = 20_000;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct BitcoinNodeStatus {
|
||||
pub ok: bool,
|
||||
pub stale: bool,
|
||||
pub updated_at_ms: u64,
|
||||
// Server-computed age of the snapshot, filled in at serve time. The browser
|
||||
// must not derive this itself (Date.now() - updated_at_ms) because that
|
||||
// compares the browser clock against this node's clock — any skew made a
|
||||
// fresh snapshot look stale and the "reconnecting…" banner never cleared.
|
||||
pub age_ms: u64,
|
||||
pub error: Option<String>,
|
||||
pub blockchain_info: Option<serde_json::Value>,
|
||||
pub network_info: Option<serde_json::Value>,
|
||||
@@ -40,6 +52,7 @@ impl Default for BitcoinNodeStatus {
|
||||
ok: false,
|
||||
stale: false,
|
||||
updated_at_ms: 0,
|
||||
age_ms: 0,
|
||||
error: Some("Connecting to Bitcoin node...".to_string()),
|
||||
blockchain_info: None,
|
||||
network_info: None,
|
||||
@@ -128,7 +141,11 @@ pub fn spawn_status_cache() {
|
||||
|
||||
if cached.blockchain_info.is_some() {
|
||||
cached.ok = false;
|
||||
cached.stale = true;
|
||||
// Only flip to "stale" once the last good snapshot is older
|
||||
// than the grace window. A brief RPC gap on a busy node keeps
|
||||
// showing last-known state silently instead of a banner flicker.
|
||||
let snapshot_age_ms = now_ms().saturating_sub(cached.updated_at_ms);
|
||||
cached.stale = snapshot_age_ms > STALE_GRACE_MS;
|
||||
cached.error = Some(friendly_transient_error(true, &err_msg));
|
||||
} else {
|
||||
*cached = BitcoinNodeStatus {
|
||||
@@ -148,12 +165,22 @@ pub fn spawn_status_cache() {
|
||||
}
|
||||
|
||||
pub async fn get_bitcoin_status() -> BitcoinNodeStatus {
|
||||
cache().read().await.clone()
|
||||
let mut status = cache().read().await.clone();
|
||||
// Compute age here (server clock only) so the browser never has to subtract
|
||||
// across clocks. A successful snapshot serves age_ms ≈ 0 → the UI clears the
|
||||
// "reconnecting…" banner on its very next poll regardless of browser-clock skew.
|
||||
if status.updated_at_ms > 0 {
|
||||
status.age_ms = now_ms().saturating_sub(status.updated_at_ms);
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
async fn fetch_bitcoin_status() -> Result<BitcoinNodeStatus> {
|
||||
// 12s (not 8s): on a swap-thrashing node getblockchaininfo can answer slowly
|
||||
// but correctly; too tight a timeout turned working-but-slow polls into
|
||||
// failures and tripped the "reconnecting…" banner. Stays under STALE_GRACE_MS.
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(8))
|
||||
.timeout(Duration::from_secs(12))
|
||||
.build()
|
||||
.context("build Bitcoin status HTTP client")?;
|
||||
|
||||
@@ -172,6 +199,7 @@ async fn fetch_bitcoin_status() -> Result<BitcoinNodeStatus> {
|
||||
ok: true,
|
||||
stale: false,
|
||||
updated_at_ms: now_ms(),
|
||||
age_ms: 0,
|
||||
error: None,
|
||||
blockchain_info: Some(blockchain_info),
|
||||
network_info: network_info.ok(),
|
||||
|
||||
Reference in New Issue
Block a user