fix: patch bitcoin receive and full-screen launch overlays
This commit is contained in:
parent
b11c6c17d1
commit
8d4b309753
@ -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.
|
||||
|
||||
@ -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 }))
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -3,16 +3,17 @@
|
||||
<Transition name="app-launcher">
|
||||
<div
|
||||
v-if="store.isOpen"
|
||||
class="fixed inset-0 z-[2400] flex items-center justify-center p-0 md:p-10"
|
||||
class="fixed inset-0 z-[2400] flex items-stretch justify-stretch p-0"
|
||||
@click.self="store.close()"
|
||||
>
|
||||
<!-- Backdrop - blur like spotlight -->
|
||||
<div class="app-launcher-backdrop absolute inset-0 bg-black/60 backdrop-blur-md"></div>
|
||||
<div class="app-launcher-backdrop absolute inset-0 bg-black/75 backdrop-blur-md"></div>
|
||||
|
||||
<!-- Panel - inset with margins, glass style like spotlight -->
|
||||
<!-- Panel - full-screen overlay -->
|
||||
<div
|
||||
class="app-launcher-panel relative z-10 flex flex-col overflow-hidden rounded-none md:rounded-2xl shadow-2xl"
|
||||
class="app-launcher-panel relative z-10 flex flex-col overflow-hidden rounded-none shadow-2xl"
|
||||
:class="panelClasses"
|
||||
style="border-radius: 0;"
|
||||
>
|
||||
<!-- Header bar - sticky on mobile -->
|
||||
<div class="sticky top-0 z-10 flex items-center gap-3 border-b border-white/10 px-4 py-3 bg-black/60 backdrop-blur-md md:bg-transparent md:backdrop-blur-none">
|
||||
@ -69,7 +70,7 @@
|
||||
<!-- Loading indicator -->
|
||||
<Transition name="content-fade">
|
||||
<div v-if="iframeLoading" class="absolute inset-0 z-10 flex items-center justify-center bg-black/40">
|
||||
<svg class="animate-spin h-8 w-8 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<svg class="animate-spin h-8 w-8 text-white/70" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
@ -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) {
|
||||
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@ -244,10 +244,10 @@
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="credentialModal.show"
|
||||
class="credential-modal-overlay fixed inset-0 z-[2700] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 md:p-6"
|
||||
class="credential-modal-overlay fixed inset-0 z-[2700] flex items-stretch justify-stretch bg-black/80 backdrop-blur-md p-0"
|
||||
@click.self="closeCredentialModal"
|
||||
>
|
||||
<div class="sideload-modal credential-modal">
|
||||
<div class="credential-modal-panel">
|
||||
<div class="flex items-start justify-between gap-4 mb-5">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">{{ credentialModal.title }}</h2>
|
||||
@ -259,7 +259,7 @@
|
||||
<div v-for="cred in credentialModal.credentials" :key="cred.label" class="rounded-lg border border-white/10 bg-white/[0.04] p-3">
|
||||
<div class="flex items-center justify-between gap-3 mb-1">
|
||||
<span class="text-white/60 text-xs uppercase tracking-wide">{{ cred.label }}</span>
|
||||
<button type="button" class="text-xs text-blue-300 hover:text-blue-200" @click="copyModalCredential(cred.label, cred.value)">{{ credentialModal.copied === cred.label ? 'Copied' : 'Copy' }}</button>
|
||||
<button type="button" class="text-xs text-orange-300 hover:text-orange-200" @click="copyModalCredential(cred.label, cred.value)">{{ credentialModal.copied === cred.label ? 'Copied' : 'Copy' }}</button>
|
||||
</div>
|
||||
<p class="font-mono text-sm text-white break-all">{{ cred.value }}</p>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -85,8 +85,8 @@
|
||||
</div>
|
||||
|
||||
<Transition name="fade">
|
||||
<div v-if="credentialModal.show" class="credential-modal-overlay fixed inset-0 z-[2700] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 md:p-6" @click.self="closeCredentialModal">
|
||||
<div class="sideload-modal credential-modal">
|
||||
<div v-if="credentialModal.show" class="credential-modal-overlay fixed inset-0 z-[2700] flex items-stretch justify-stretch bg-black/80 backdrop-blur-md p-0" @click.self="closeCredentialModal">
|
||||
<div class="credential-modal-panel">
|
||||
<div class="flex items-start justify-between gap-4 mb-5">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-white">{{ credentialModal.title }}</h2>
|
||||
@ -98,7 +98,7 @@
|
||||
<div v-for="cred in credentialModal.credentials" :key="cred.label" class="rounded-lg border border-white/10 bg-white/[0.04] p-3">
|
||||
<div class="flex items-center justify-between gap-3 mb-1">
|
||||
<span class="text-white/60 text-xs uppercase tracking-wide">{{ cred.label }}</span>
|
||||
<button type="button" class="text-xs text-blue-300 hover:text-blue-200" @click="copyModalCredential(cred.label, cred.value)">{{ credentialModal.copied === cred.label ? 'Copied' : 'Copy' }}</button>
|
||||
<button type="button" class="text-xs text-orange-300 hover:text-orange-200" @click="copyModalCredential(cred.label, cred.value)">{{ credentialModal.copied === cred.label ? 'Copied' : 'Copy' }}</button>
|
||||
</div>
|
||||
<p class="font-mono text-sm text-white break-all">{{ cred.value }}</p>
|
||||
</div>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user