Compare commits

...

2 Commits

Author SHA1 Message Date
archipelago
6fd1cf9ba7 chore: release v1.7.87-alpha 2026-06-12 04:49:58 -04:00
archipelago
8d4b309753 fix: patch bitcoin receive and full-screen launch overlays 2026-06-12 04:42:23 -04:00
12 changed files with 101 additions and 84 deletions

View File

@ -1,5 +1,11 @@
# Changelog # 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) ## 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. - 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.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "archipelago" name = "archipelago"
version = "1.7.86-alpha" version = "1.7.87-alpha"
edition = "2021" edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend" description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"] authors = ["Archipelago Team"]

View File

@ -12,23 +12,28 @@ impl RpcHandler {
let (client, macaroon_hex) = self.lnd_client().await?; let (client, macaroon_hex) = self.lnd_client().await?;
let resp = client let resp = client
.get(format!("{LND_REST_BASE_URL}/v1/newaddress")) .post(format!("{LND_REST_BASE_URL}/v1/newaddress"))
.query(&[("type", "WITNESS_PUBKEY_HASH")])
.header("Grpc-Metadata-macaroon", &macaroon_hex) .header("Grpc-Metadata-macaroon", &macaroon_hex)
.json(&serde_json::json!({ "type": "WITNESS_PUBKEY_HASH" }))
.send() .send()
.await .await
.context("LND REST connection failed")?; .context("LND REST connection failed")?;
let status = resp.status(); let status = resp.status();
let body: serde_json::Value = resp let raw_body = resp
.json() .text()
.await .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() { if !status.is_success() {
let message = lnd_error_message(&body); let message = lnd_error_message(&body);
anyhow::bail!( anyhow::bail!(
"LND could not generate a Bitcoin address ({}): {}", "Bitcoin address generation failed ({}): {}",
status, status,
message message
); );
@ -39,14 +44,14 @@ impl RpcHandler {
.or_else(|| body.get("message")) .or_else(|| body.get("message"))
.and_then(|v| v.as_str()) .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 let address = body
.get("address") .get("address")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.filter(|addr| !addr.trim().is_empty()) .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(); .to_string();
Ok(serde_json::json!({ "address": address })) Ok(serde_json::json!({ "address": address }))

View File

@ -63,6 +63,7 @@ pub(super) fn sanitize_error_message(msg: &str) -> String {
"Failed to start", "Failed to start",
"Container", "Container",
"Image", "Image",
"Bitcoin address",
]; ];
for prefix in &user_facing_prefixes { for prefix in &user_facing_prefixes {
if msg.starts_with(prefix) { if msg.starts_with(prefix) {

View File

@ -1,12 +1,12 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"version": "1.7.86-alpha", "version": "1.7.87-alpha",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "neode-ui", "name": "neode-ui",
"version": "1.7.86-alpha", "version": "1.7.87-alpha",
"dependencies": { "dependencies": {
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1", "@vue-leaflet/vue-leaflet": "^0.10.1",

View File

@ -1,7 +1,7 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"private": true, "private": true,
"version": "1.7.86-alpha", "version": "1.7.87-alpha",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "./start-dev.sh", "start": "./start-dev.sh",

View File

@ -3,16 +3,17 @@
<Transition name="app-launcher"> <Transition name="app-launcher">
<div <div
v-if="store.isOpen" 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()" @click.self="store.close()"
> >
<!-- Backdrop - blur like spotlight --> <!-- 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 <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" :class="panelClasses"
style="border-radius: 0;"
> >
<!-- Header bar - sticky on mobile --> <!-- 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"> <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 --> <!-- Loading indicator -->
<Transition name="content-fade"> <Transition name="content-fade">
<div v-if="iframeLoading" class="absolute inset-0 z-10 flex items-center justify-center bg-black/40"> <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> <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> <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> </svg>
@ -398,7 +399,7 @@ function injectScrollbarHideIfSameOrigin() {
const panelClasses = [ const panelClasses = [
'glass-card', 'glass-card',
'w-full h-full', '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) { function onKeyDown(e: KeyboardEvent) {

View File

@ -17,4 +17,8 @@ describe('explainReceiveAddressFailure', () => {
it('explains lnd transport failures', () => { it('explains lnd transport failures', () => {
expect(explainReceiveAddressFailure(new Error('LND REST connection failed'))).toContain('not responding cleanly') 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')
})
}) })

View File

@ -244,10 +244,10 @@
<Transition name="fade"> <Transition name="fade">
<div <div
v-if="credentialModal.show" 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" @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 class="flex items-start justify-between gap-4 mb-5">
<div> <div>
<h2 class="text-lg font-semibold text-white">{{ credentialModal.title }}</h2> <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 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"> <div class="flex items-center justify-between gap-3 mb-1">
<span class="text-white/60 text-xs uppercase tracking-wide">{{ cred.label }}</span> <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> </div>
<p class="font-mono text-sm text-white break-all">{{ cred.value }}</p> <p class="font-mono text-sm text-white break-all">{{ cred.value }}</p>
</div> </div>
@ -802,15 +802,24 @@ async function submitSideload() {
} }
.sideload-input::placeholder { color: rgba(255, 255, 255, 0.38); } .sideload-input::placeholder { color: rgba(255, 255, 255, 0.38); }
.sideload-input:focus { border-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; display: flex;
flex-direction: column; 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); width: 100%;
border-radius: 1.25rem; height: 100%;
padding-bottom: 1.25rem; min-height: 0;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); 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 { .credential-modal-body {
flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
@ -818,11 +827,4 @@ async function submitSideload() {
.credential-modal-actions { .credential-modal-actions {
flex-shrink: 0; 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> </style>

View File

@ -85,8 +85,8 @@
</div> </div>
<Transition name="fade"> <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 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="sideload-modal credential-modal"> <div class="credential-modal-panel">
<div class="flex items-start justify-between gap-4 mb-5"> <div class="flex items-start justify-between gap-4 mb-5">
<div> <div>
<h2 class="text-lg font-semibold text-white">{{ credentialModal.title }}</h2> <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 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"> <div class="flex items-center justify-between gap-3 mb-1">
<span class="text-white/60 text-xs uppercase tracking-wide">{{ cred.label }}</span> <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> </div>
<p class="font-mono text-sm text-white break-all">{{ cred.value }}</p> <p class="font-mono text-sm text-white break-all">{{ cred.value }}</p>
</div> </div>
@ -328,24 +328,28 @@ function scrollToPage(index: number) {
background: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.06);
} }
.credential-modal-body { .credential-modal-body {
flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.credential-modal { .credential-modal-panel {
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); display: flex;
border-radius: 1.25rem; flex-direction: column;
padding-bottom: 1.25rem; width: 100%;
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.55); 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 { .credential-modal-actions {
flex-shrink: 0; 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> </style>

View File

@ -1,30 +1,27 @@
{ {
"version": "1.7.86-alpha", "version": "1.7.87-alpha",
"release_date": "2026-06-12", "release_date": "2026-06-12",
"changelog": [ "changelog": [
"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.", "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.",
"Connected nodes and identities now reuse their last loaded data instead of reloading the visible list every time the user revisits the tab.", "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.",
"The Fleet matrix and detail views now show actual node names and host information instead of raw node id prefixes.", "Validation passed with `git diff --check`, `npm run type-check`, and the focused frontend tests for `bitcoinReceive` and `AppIconGrid`."
"The network map only redraws when its graph data actually changes, which stops the D3 scene from visually resetting on every refresh tick.",
"Mobile federation and system-update actions now stack full width, and the ElectrumX app health check allows a long startup window so slow sync nodes do not restart mid-index.",
"Validation passed with `git diff --check`, focused frontend tests, and `npm run type-check`."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.86-alpha", "current_version": "1.7.87-alpha",
"new_version": "1.7.86-alpha", "new_version": "1.7.87-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.86-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.87-alpha/archipelago",
"sha256": "2eb936fe188df25947a2d626af1b22be2f7ef9dcbc927542389de3b38e80f9e6", "sha256": "a87bf3245e3ae67baf8c7018e89794e8bd43b18ee3858dda65364562f2927045",
"size_bytes": 44050232 "size_bytes": 44058416
}, },
{ {
"name": "archipelago-frontend-1.7.86-alpha.tar.gz", "name": "archipelago-frontend-1.7.87-alpha.tar.gz",
"current_version": "1.7.86-alpha", "current_version": "1.7.87-alpha",
"new_version": "1.7.86-alpha", "new_version": "1.7.87-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.86-alpha/archipelago-frontend-1.7.86-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.87-alpha/archipelago-frontend-1.7.87-alpha.tar.gz",
"sha256": "9f6f146eaf709cd3e778550bb7a56877dce551cb6d631bccf08e80ed977dd17b", "sha256": "df0834a4e9c45e608de2fa5f0031f939d49eec0b6b8d1fd9a422676ae96b3dbe",
"size_bytes": 184060614 "size_bytes": 184059938
} }
] ]
} }

View File

@ -1,30 +1,27 @@
{ {
"version": "1.7.86-alpha", "version": "1.7.87-alpha",
"release_date": "2026-06-12", "release_date": "2026-06-12",
"changelog": [ "changelog": [
"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.", "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.",
"Connected nodes and identities now reuse their last loaded data instead of reloading the visible list every time the user revisits the tab.", "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.",
"The Fleet matrix and detail views now show actual node names and host information instead of raw node id prefixes.", "Validation passed with `git diff --check`, `npm run type-check`, and the focused frontend tests for `bitcoinReceive` and `AppIconGrid`."
"The network map only redraws when its graph data actually changes, which stops the D3 scene from visually resetting on every refresh tick.",
"Mobile federation and system-update actions now stack full width, and the ElectrumX app health check allows a long startup window so slow sync nodes do not restart mid-index.",
"Validation passed with `git diff --check`, focused frontend tests, and `npm run type-check`."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.86-alpha", "current_version": "1.7.87-alpha",
"new_version": "1.7.86-alpha", "new_version": "1.7.87-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.86-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.87-alpha/archipelago",
"sha256": "2eb936fe188df25947a2d626af1b22be2f7ef9dcbc927542389de3b38e80f9e6", "sha256": "a87bf3245e3ae67baf8c7018e89794e8bd43b18ee3858dda65364562f2927045",
"size_bytes": 44050232 "size_bytes": 44058416
}, },
{ {
"name": "archipelago-frontend-1.7.86-alpha.tar.gz", "name": "archipelago-frontend-1.7.87-alpha.tar.gz",
"current_version": "1.7.86-alpha", "current_version": "1.7.87-alpha",
"new_version": "1.7.86-alpha", "new_version": "1.7.87-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.86-alpha/archipelago-frontend-1.7.86-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.87-alpha/archipelago-frontend-1.7.87-alpha.tar.gz",
"sha256": "9f6f146eaf709cd3e778550bb7a56877dce551cb6d631bccf08e80ed977dd17b", "sha256": "df0834a4e9c45e608de2fa5f0031f939d49eec0b6b8d1fd9a422676ae96b3dbe",
"size_bytes": 184060614 "size_bytes": 184059938
} }
] ]
} }