diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f8191a..447bac9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.7.87-alpha (2026-06-12) + +- Bitcoin receive now calls LND's on-chain address endpoint with the correct REST method, and backend failures keep the specific address-generation error instead of collapsing into the generic operation-failed message. +- App launch credential interstitials now render as true full-screen overlays, and the launcher loading indicator uses the neutral brand palette instead of a blue spinner. +- Validation passed with `git diff --check`, `npm run type-check`, and the focused frontend tests for `bitcoinReceive` and `AppIconGrid`. + ## v1.7.86-alpha (2026-06-12) - Fleet now preserves the last known node list, alerts, and selection locally while telemetry refreshes in the background, so the dashboard no longer blanks on tab switches or update scans. diff --git a/core/archipelago/src/api/rpc/lnd/wallet.rs b/core/archipelago/src/api/rpc/lnd/wallet.rs index e93fa91f..fbc06414 100644 --- a/core/archipelago/src/api/rpc/lnd/wallet.rs +++ b/core/archipelago/src/api/rpc/lnd/wallet.rs @@ -12,23 +12,28 @@ impl RpcHandler { let (client, macaroon_hex) = self.lnd_client().await?; let resp = client - .get(format!("{LND_REST_BASE_URL}/v1/newaddress")) - .query(&[("type", "WITNESS_PUBKEY_HASH")]) + .post(format!("{LND_REST_BASE_URL}/v1/newaddress")) .header("Grpc-Metadata-macaroon", &macaroon_hex) + .json(&serde_json::json!({ "type": "WITNESS_PUBKEY_HASH" })) .send() .await .context("LND REST connection failed")?; let status = resp.status(); - let body: serde_json::Value = resp - .json() + let raw_body = resp + .text() .await - .context("Failed to parse newaddress response")?; + .context("LND address response could not be read")?; + let body: serde_json::Value = serde_json::from_str(&raw_body).unwrap_or_else(|_| { + serde_json::json!({ + "raw": raw_body, + }) + }); if !status.is_success() { let message = lnd_error_message(&body); anyhow::bail!( - "LND could not generate a Bitcoin address ({}): {}", + "Bitcoin address generation failed ({}): {}", status, message ); @@ -39,14 +44,14 @@ impl RpcHandler { .or_else(|| body.get("message")) .and_then(|v| v.as_str()) { - anyhow::bail!("LND could not generate a Bitcoin address: {}", error); + anyhow::bail!("Bitcoin address generation failed: {}", error); } let address = body .get("address") .and_then(|v| v.as_str()) .filter(|addr| !addr.trim().is_empty()) - .ok_or_else(|| anyhow::anyhow!("LND did not return a Bitcoin address. The wallet may still be locked, uninitialized, or waiting for Bitcoin to sync."))? + .ok_or_else(|| anyhow::anyhow!("Bitcoin address generation failed: LND did not return a Bitcoin address. The wallet may still be locked, uninitialized, or waiting for Bitcoin to sync."))? .to_string(); Ok(serde_json::json!({ "address": address })) diff --git a/core/archipelago/src/api/rpc/middleware.rs b/core/archipelago/src/api/rpc/middleware.rs index 15598bde..8cef041e 100644 --- a/core/archipelago/src/api/rpc/middleware.rs +++ b/core/archipelago/src/api/rpc/middleware.rs @@ -63,6 +63,7 @@ pub(super) fn sanitize_error_message(msg: &str) -> String { "Failed to start", "Container", "Image", + "Bitcoin address", ]; for prefix in &user_facing_prefixes { if msg.starts_with(prefix) { diff --git a/neode-ui/src/components/AppLauncherOverlay.vue b/neode-ui/src/components/AppLauncherOverlay.vue index 7d68eac3..ee23ac24 100644 --- a/neode-ui/src/components/AppLauncherOverlay.vue +++ b/neode-ui/src/components/AppLauncherOverlay.vue @@ -3,16 +3,17 @@
-
+
- +
@@ -69,7 +70,7 @@
- + @@ -398,7 +399,7 @@ function injectScrollbarHideIfSameOrigin() { const panelClasses = [ 'glass-card', 'w-full h-full', - 'md:max-w-[calc(100vw-5rem)] md:max-h-[calc(100vh-5rem)]', + 'max-w-none max-h-none', ] function onKeyDown(e: KeyboardEvent) { diff --git a/neode-ui/src/utils/__tests__/bitcoinReceive.test.ts b/neode-ui/src/utils/__tests__/bitcoinReceive.test.ts index ec7832a4..dbc74c16 100644 --- a/neode-ui/src/utils/__tests__/bitcoinReceive.test.ts +++ b/neode-ui/src/utils/__tests__/bitcoinReceive.test.ts @@ -17,4 +17,8 @@ describe('explainReceiveAddressFailure', () => { it('explains lnd transport failures', () => { expect(explainReceiveAddressFailure(new Error('LND REST connection failed'))).toContain('not responding cleanly') }) + + it('keeps bitcoin address generation failures visible', () => { + expect(explainReceiveAddressFailure(new Error('Bitcoin address generation failed: LND is not ready'))).toContain('Bitcoin address generation failed') + }) }) diff --git a/neode-ui/src/views/Apps.vue b/neode-ui/src/views/Apps.vue index dfcc1a22..b8897e01 100644 --- a/neode-ui/src/views/Apps.vue +++ b/neode-ui/src/views/Apps.vue @@ -244,10 +244,10 @@
-
+

{{ credentialModal.title }}

@@ -259,7 +259,7 @@
{{ cred.label }} - +

{{ cred.value }}

@@ -802,15 +802,24 @@ async function submitSideload() { } .sideload-input::placeholder { color: rgba(255, 255, 255, 0.38); } .sideload-input:focus { border-color: rgba(255, 255, 255, 0.38); } -.credential-modal { +.credential-modal-panel { display: flex; flex-direction: column; - max-height: calc(100dvh - var(--safe-area-top, env(safe-area-inset-top, 0px)) - var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) - 2rem); - border-radius: 1.25rem; - padding-bottom: 1.25rem; - box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); + width: 100%; + height: 100%; + min-height: 0; + max-width: none; + max-height: none; + overflow: hidden; + border: 0; + border-radius: 0; + background: rgba(8, 10, 18, 0.98); + padding: 1.25rem; + padding-bottom: calc(1.25rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px))); + box-shadow: none; } .credential-modal-body { + flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; @@ -818,11 +827,4 @@ async function submitSideload() { .credential-modal-actions { flex-shrink: 0; } -@media (min-width: 768px) { - .sideload-modal { - border-radius: 1.25rem; - padding: 1.5rem; - box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); - } -} diff --git a/neode-ui/src/views/apps/AppIconGrid.vue b/neode-ui/src/views/apps/AppIconGrid.vue index 50254ff0..61e73ba8 100644 --- a/neode-ui/src/views/apps/AppIconGrid.vue +++ b/neode-ui/src/views/apps/AppIconGrid.vue @@ -85,8 +85,8 @@
-
-
+
+

{{ credentialModal.title }}

@@ -98,7 +98,7 @@
{{ cred.label }} - +

{{ cred.value }}

@@ -328,24 +328,28 @@ function scrollToPage(index: number) { background: rgba(255, 255, 255, 0.06); } .credential-modal-body { + flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; } -.credential-modal { - max-height: calc(100dvh - var(--safe-area-top, env(safe-area-inset-top, 0px)) - var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) - 2rem); - border-radius: 1.25rem; - padding-bottom: 1.25rem; - box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); +.credential-modal-panel { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 0; + max-width: none; + max-height: none; + overflow: hidden; + border: 0; + border-radius: 0; + background: rgba(8, 10, 18, 0.98); + padding: 1.25rem; + padding-bottom: calc(1.25rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px))); + box-shadow: none; } .credential-modal-actions { flex-shrink: 0; } -@media (min-width: 768px) { - .sideload-modal { - border-radius: 1.25rem; - padding: 1.5rem; - box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); - } -}