Compare commits

..

No commits in common. "eeb08fc78feeb0d95476e0e29c9af9782c68c990" and "3e01e57c8d710a466eefab183f46c5ba1a53ca3b" have entirely different histories.

17 changed files with 61 additions and 176 deletions

View File

@ -1,13 +1,5 @@
# Changelog
## v1.7.73-alpha (2026-05-19)
- Mobile app launches for iframe-blocked apps now open the direct app URL in a new browser tab immediately instead of landing in a broken in-shell webview that requires a second tap.
- Mobile My Apps/Websites tabs now react to route query changes, App Store pages label the mobile view as Discover, mobile filters have safe bottom spacing, and App Store search ignores the current category so searches cover all available apps.
- My Apps search now surfaces matching App Store entries when the app is not installed, making it possible to jump directly from a failed My Apps search to the installable app details.
- NetBird self-host installs now prefer a `100.x` tailnet/CGNAT address for dashboard, management, relay, STUN, and auth redirect origins when one is present; live repair on `100.89.209.89` updated the existing stack from LAN origins to `100.89.209.89` and restored `netbird-server`.
- App-session iframe frames now focus automatically and wrap the iframe in a scroll host so wheel/touch scrolling works in the active right frame without requiring an initial click.
## v1.7.72-alpha (2026-05-19)
- Settings What's New now includes the missing release notes for `v1.7.68-alpha` through `v1.7.71-alpha`, so the modal reflects the current OTA history instead of stopping at `v1.7.67-alpha`.

View File

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

View File

@ -1404,9 +1404,7 @@ impl RpcHandler {
.await
.context("Failed to create NetBird data directory")?;
let host_ip = detect_netbird_public_host_ip()
.await
.unwrap_or_else(|| self.config.host_ip.clone());
let host_ip = self.config.host_ip.clone();
let dashboard_origin = format!("http://{}:8087", host_ip);
let mgmt_origin = format!("http://{}:8086", host_ip);
let relay_secret = read_or_generate_b64_secret("netbird-relay-auth-secret").await;
@ -1546,19 +1544,6 @@ async fn read_or_generate_b64_secret(name: &str) -> String {
secret
}
async fn detect_netbird_public_host_ip() -> Option<String> {
let output = tokio::process::Command::new("hostname")
.args(["-I"])
.output()
.await
.ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
stdout
.split_whitespace()
.find(|ip| ip.starts_with("100.") && ip.contains('.'))
.map(str::to_string)
}
#[cfg(test)]
mod tests {
use super::{btcpay_stack_app_ids, mempool_stack_app_ids};

View File

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

View File

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

View File

@ -1659,15 +1659,6 @@ html:has(body.video-background-active)::before {
filter: drop-shadow(0 10px 25px rgba(0, 0, 0, 0.5));
}
.mobile-filter-btn {
bottom: calc(var(--mobile-tab-bar-height, 72px) + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 12px);
filter: drop-shadow(0 10px 25px rgba(0, 0, 0, 0.5));
}
.mobile-filter-sheet {
padding-bottom: calc(var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + 1.5rem);
}
/* ── Cloud Audio Player (mini bar) ──── */
.cloud-audio-player {

View File

@ -341,8 +341,9 @@ watch(displayMode, (mode) => {
})
onMounted(() => {
// Apps that block iframes open externally instead of landing in a broken webview.
if (mustOpenNewTab.value && appUrl.value) {
// Desktop apps that block iframes open externally. Mobile keeps the user in
// Archipelago and shows the explicit fallback instead of leaving the shell.
if (!isMobile && mustOpenNewTab.value && appUrl.value) {
window.open(appUrl.value, '_blank', 'noopener,noreferrer')
if (isInlinePanel.value) emit('close')
else closeRouteSession()
@ -531,12 +532,6 @@ onBeforeUnmount(() => {
opacity: 0;
}
.app-session-frame-scroll-host {
overflow: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
}
/* Mobile: full-bleed app sessions — no border, no radius, no shadow */
@media (max-width: 767px) {
.app-session-root {

View File

@ -106,34 +106,10 @@
</div>
<!-- No Results -->
<div v-if="filteredPackageEntries.length === 0 && marketplaceMatches.length === 0 && searchQuery" class="text-center py-12">
<div v-if="filteredPackageEntries.length === 0 && searchQuery" class="text-center py-12">
<p class="text-white/70">{{ t('apps.noResults', { query: searchQuery }) }}</p>
</div>
<div v-if="marketplaceMatches.length > 0" class="mb-5">
<div class="flex items-center gap-3 mb-3">
<span class="discover-terminal-tag">app store</span>
<h2 class="text-lg font-bold text-white">Available in Discover</h2>
<div class="flex-1 h-px bg-white/10"></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<button
v-for="app in marketplaceMatches"
:key="app.id"
type="button"
class="glass-card p-4 text-left flex items-center gap-3 hover:bg-orange-500/5 hover:border-orange-500/15 transition-colors"
@click="openMarketplaceResult(app)"
>
<img v-if="app.icon" :src="app.icon" :alt="app.title" class="w-12 h-12 rounded-xl object-cover bg-white/10" />
<div v-else class="w-12 h-12 rounded-xl bg-white/10 flex-shrink-0"></div>
<div class="min-w-0 flex-1">
<p class="font-semibold text-white truncate">{{ app.title }}</p>
<p class="text-xs text-white/50 truncate">Available in App Store</p>
</div>
</button>
</div>
</div>
<!-- Mobile: iPhone-style icon grid -->
<div class="md:hidden">
<AppIconGrid
@ -260,12 +236,10 @@ import AppCard from './apps/AppCard.vue'
import AppIconGrid from './apps/AppIconGrid.vue'
import AppsUninstallModal from './apps/AppsUninstallModal.vue'
import { useAppsActions } from './apps/useAppsActions'
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
import {
type AppsTab, filterEntriesForTab, isWebOnlyApp, isWebsitePackage, opensInTab, resolveRuntimeLaunchUrl,
WEB_ONLY_APPS, WEB_ONLY_APP_URLS, buildAllCategories, useCategoriesWithApps,
} from './apps/appsConfig'
import { getCuratedAppList, INSTALLED_ALIASES, type MarketplaceApp } from './marketplace/marketplaceData'
const { t } = useI18n()
const router = useRouter()
@ -273,7 +247,6 @@ const route = useRoute()
const store = useAppStore()
const serverStore = useServerStore()
const actions = useAppsActions()
const { setCurrentApp } = useMarketplaceApp()
const showSideload = ref(false)
const sideloading = ref(false)
const sideloadError = ref('')
@ -293,10 +266,6 @@ const activeTab = ref<AppsTab>(
route.query.tab === 'websites' || route.query.tab === 'services' ? 'websites' : 'apps'
)
watch(() => route.query.tab, (tab) => {
activeTab.value = tab === 'websites' || tab === 'services' ? 'websites' : 'apps'
})
// Search (debounced)
const searchQuery = ref('')
const debouncedSearchQuery = ref('')
@ -340,19 +309,6 @@ const packages = computed(() => {
const categoriesWithApps = useCategoriesWithApps(packages, ALL_CATEGORIES)
const curatedApps = getCuratedAppList()
const marketplaceMatches = computed(() => {
const q = debouncedSearchQuery.value.trim().toLowerCase()
if (!q || activeTab.value !== 'apps') return [] as MarketplaceApp[]
return curatedApps.filter(app => {
if (isInstalledInMyApps(app.id)) return false
return app.title?.toLowerCase().includes(q) ||
app.id.toLowerCase().includes(q) ||
app.author?.toLowerCase().includes(q) ||
(typeof app.description === 'string' && app.description.toLowerCase().includes(q))
}).slice(0, 6)
})
const isLoadingApps = computed(() => !store.hasLoadedInitialData && !connectionError.value)
// Connection error state
@ -396,17 +352,6 @@ const filteredPackageEntries = computed(() => {
)
})
function isInstalledInMyApps(appId: string): boolean {
if (appId in packages.value) return true
const aliases = INSTALLED_ALIASES[appId]
return aliases ? aliases.some(alias => alias in packages.value) : false
}
function openMarketplaceResult(app: MarketplaceApp) {
setCurrentApp(app)
router.push({ name: 'marketplace-app-detail', params: { id: app.id }, query: { from: 'apps' } }).catch(() => {})
}
// Uninstall modal
const uninstallModal = ref({ show: false, appId: '', appTitle: '' })

View File

@ -35,10 +35,6 @@
<!-- Mobile: search -->
<div class="md:hidden mb-4">
<div class="flex items-center gap-2 mb-3">
<span class="discover-terminal-tag">discover</span>
<h1 class="text-lg font-bold text-white">App Store</h1>
</div>
<input
v-model="searchQuery"
type="text"
@ -316,9 +312,6 @@ const categoriesWithApps = computed(() => {
const filteredApps = computed(() => {
let apps = allApps.value
if (selectedCategory.value && selectedCategory.value !== 'all' && !searchQuery.value) {
apps = apps.filter(app => app.category === selectedCategory.value)
}
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
apps = apps.filter(app =>

View File

@ -37,10 +37,6 @@
<!-- Mobile: search (tabs handled by Dashboard.vue header) -->
<div class="md:hidden mb-4">
<div class="flex items-center gap-2 mb-3">
<span class="discover-terminal-tag">discover</span>
<h1 class="text-lg font-bold text-white">App Store</h1>
</div>
<input
v-model="searchQuery"
type="text"
@ -262,7 +258,7 @@ const categoriesWithApps = computed(() => {
const filteredApps = computed(() => {
let apps = allApps.value
if (selectedCategory.value && selectedCategory.value !== 'all' && !searchQuery.value) {
if (selectedCategory.value && selectedCategory.value !== 'all') {
apps = apps.filter(app => app.category === selectedCategory.value)
}

View File

@ -9,18 +9,16 @@
</div>
</Transition>
<div v-if="appUrl && !iframeBlocked" class="absolute inset-0 app-session-frame-scroll-host">
<iframe
ref="iframeRef"
:key="refreshKey"
:src="appUrl"
class="w-full h-full border-0 iframe-scrollbar-hide"
title="App content"
tabindex="0"
@load="$emit('iframeLoad')"
@error="$emit('iframeError')"
/>
</div>
<iframe
v-if="appUrl && !iframeBlocked"
ref="iframeRef"
:key="refreshKey"
:src="appUrl"
class="absolute inset-0 w-full h-full border-0 iframe-scrollbar-hide"
title="App content"
@load="$emit('iframeLoad')"
@error="$emit('iframeError')"
/>
<!-- Iframe blocked fallback -->
<Transition name="content-fade">
@ -71,9 +69,9 @@
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import { ref } from 'vue'
const props = defineProps<{
defineProps<{
appUrl: string
appId: string
appTitle: string
@ -93,11 +91,5 @@ defineEmits<{
const iframeRef = ref<HTMLIFrameElement | null>(null)
watch(() => [props.appUrl, props.refreshKey, props.iframeBlocked], async () => {
if (!props.appUrl || props.iframeBlocked) return
await nextTick()
iframeRef.value?.focus({ preventScroll: true })
}, { immediate: true })
defineExpose({ iframeRef })
</script>

View File

@ -61,7 +61,7 @@ describe('AppIconGrid', () => {
expect(useAppLauncherStore().panelAppId).toBe('lnd')
})
it('routes desktop new-tab apps through app session on mobile', async () => {
it('opens desktop new-tab apps through app session on mobile', async () => {
Object.defineProperty(window, 'innerWidth', {
value: 390,
writable: true,
@ -78,6 +78,5 @@ describe('AppIconGrid', () => {
await wrapper.get('.app-icon-item').trigger('click')
expect(mockWindowOpen).not.toHaveBeenCalled()
expect(useAppLauncherStore().panelAppId).toBeNull()
})
})

View File

@ -22,7 +22,6 @@
to="/dashboard/apps?tab=websites"
class="mode-switcher-btn"
:class="{ 'mode-switcher-btn-active': route.query.tab === 'services' || route.query.tab === 'websites' }"
@click.prevent="router.push({ path: '/dashboard/apps', query: { tab: 'websites' } })"
>Websites</RouterLink>
</div>
</div>

View File

@ -4,7 +4,8 @@
<Teleport to="body">
<button
@click="showFilter = true"
class="md:hidden fixed right-4 z-[2400] w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-filter-btn"
class="md:hidden fixed right-4 z-40 w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-back-btn"
style="left: auto;"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
@ -16,10 +17,10 @@
<Transition name="modal">
<div
v-if="showFilter"
class="fixed inset-0 z-[3000] flex items-end justify-center md:hidden bg-black/10 backdrop-blur-md"
class="fixed inset-0 z-50 flex items-end justify-center md:hidden bg-black/10 backdrop-blur-md"
@click.self="closeFilter"
>
<div ref="filterModalRef" class="glass-card p-6 w-full rounded-t-3xl max-h-[80vh] overflow-y-auto mobile-filter-sheet">
<div ref="filterModalRef" class="glass-card p-6 w-full rounded-t-3xl max-h-[80vh] overflow-y-auto">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white">Filter</h2>
<button @click="closeFilter" class="text-white/60 hover:text-white transition-colors">

View File

@ -3,7 +3,8 @@
<Teleport to="body">
<button
@click="showModal = true"
class="md:hidden fixed right-4 z-[2400] w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-filter-btn"
class="md:hidden fixed right-4 z-40 w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-back-btn"
style="left: auto;"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
@ -15,10 +16,10 @@
<Transition name="modal">
<div
v-if="showModal"
class="fixed inset-0 z-[3000] flex items-end justify-center md:hidden bg-black/10 backdrop-blur-md"
class="fixed inset-0 z-50 flex items-end justify-center md:hidden bg-black/10 backdrop-blur-md"
@click.self="close()"
>
<div ref="modalRef" class="glass-card p-6 w-full rounded-t-3xl max-h-[80vh] overflow-y-auto mobile-filter-sheet">
<div ref="modalRef" class="glass-card p-6 w-full rounded-t-3xl max-h-[80vh] overflow-y-auto">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-white">{{ t('marketplace.filterByCategory') }}</h2>

View File

@ -1,29 +1,27 @@
{
"version": "1.7.73-alpha",
"version": "1.7.72-alpha",
"release_date": "2026-05-19",
"changelog": [
"Mobile app launches for iframe-blocked apps now open the direct app URL in a new browser tab immediately instead of landing in a broken in-shell webview that requires a second tap.",
"Mobile My Apps/Websites tabs now react to route query changes, App Store pages label the mobile view as Discover, mobile filters have safe bottom spacing, and App Store search ignores the current category so searches cover all available apps.",
"My Apps search now surfaces matching App Store entries when the app is not installed, making it possible to jump directly from a failed My Apps search to the installable app details.",
"NetBird self-host installs now prefer a `100.x` tailnet/CGNAT address for dashboard, management, relay, STUN, and auth redirect origins when one is present; live repair on `100.89.209.89` updated the existing stack from LAN origins to `100.89.209.89` and restored `netbird-server`.",
"App-session iframe frames now focus automatically and wrap the iframe in a scroll host so wheel/touch scrolling works in the active right frame without requiring an initial click."
"Settings What's New now includes the missing release notes for `v1.7.68-alpha` through `v1.7.71-alpha`, so the modal reflects the current OTA history instead of stopping at `v1.7.67-alpha`.",
"The follow-up release carries the NetBird install fix, Gitea icon polish, mobile app-session fallback updates, and rounder app icon masks from `v1.7.71-alpha` with the Settings modal notes included.",
"The local Cargo lockfile version metadata is kept in sync with the release bump after the previous release build updated it."
],
"components": [
{
"name": "archipelago",
"current_version": "1.7.73-alpha",
"new_version": "1.7.73-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.73-alpha/archipelago",
"sha256": "458b510e34c0d69e39294ff05eb56ed89c2eb896b72074ecaea0c2bb52477455",
"size_bytes": 42997664
"current_version": "1.7.72-alpha",
"new_version": "1.7.72-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.72-alpha/archipelago",
"sha256": "f0feae864d58b0204077e9559217f51c07e270f31c45c6ee05d7bf3086477c7e",
"size_bytes": 42990584
},
{
"name": "archipelago-frontend-1.7.73-alpha.tar.gz",
"current_version": "1.7.73-alpha",
"new_version": "1.7.73-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.73-alpha/archipelago-frontend-1.7.73-alpha.tar.gz",
"sha256": "586ee104787b8c69230f2616dd6256926299907fde53d6dd6f0015b98c5db41b",
"size_bytes": 166488057
"name": "archipelago-frontend-1.7.72-alpha.tar.gz",
"current_version": "1.7.72-alpha",
"new_version": "1.7.72-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.72-alpha/archipelago-frontend-1.7.72-alpha.tar.gz",
"sha256": "26e26be4f804685694e5885cf4434ae0ecf0d24c4a5cebd32a81239be1517789",
"size_bytes": 166481709
}
]
}

View File

@ -1,29 +1,27 @@
{
"version": "1.7.73-alpha",
"version": "1.7.72-alpha",
"release_date": "2026-05-19",
"changelog": [
"Mobile app launches for iframe-blocked apps now open the direct app URL in a new browser tab immediately instead of landing in a broken in-shell webview that requires a second tap.",
"Mobile My Apps/Websites tabs now react to route query changes, App Store pages label the mobile view as Discover, mobile filters have safe bottom spacing, and App Store search ignores the current category so searches cover all available apps.",
"My Apps search now surfaces matching App Store entries when the app is not installed, making it possible to jump directly from a failed My Apps search to the installable app details.",
"NetBird self-host installs now prefer a `100.x` tailnet/CGNAT address for dashboard, management, relay, STUN, and auth redirect origins when one is present; live repair on `100.89.209.89` updated the existing stack from LAN origins to `100.89.209.89` and restored `netbird-server`.",
"App-session iframe frames now focus automatically and wrap the iframe in a scroll host so wheel/touch scrolling works in the active right frame without requiring an initial click."
"Settings What's New now includes the missing release notes for `v1.7.68-alpha` through `v1.7.71-alpha`, so the modal reflects the current OTA history instead of stopping at `v1.7.67-alpha`.",
"The follow-up release carries the NetBird install fix, Gitea icon polish, mobile app-session fallback updates, and rounder app icon masks from `v1.7.71-alpha` with the Settings modal notes included.",
"The local Cargo lockfile version metadata is kept in sync with the release bump after the previous release build updated it."
],
"components": [
{
"name": "archipelago",
"current_version": "1.7.73-alpha",
"new_version": "1.7.73-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.73-alpha/archipelago",
"sha256": "458b510e34c0d69e39294ff05eb56ed89c2eb896b72074ecaea0c2bb52477455",
"size_bytes": 42997664
"current_version": "1.7.72-alpha",
"new_version": "1.7.72-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.72-alpha/archipelago",
"sha256": "f0feae864d58b0204077e9559217f51c07e270f31c45c6ee05d7bf3086477c7e",
"size_bytes": 42990584
},
{
"name": "archipelago-frontend-1.7.73-alpha.tar.gz",
"current_version": "1.7.73-alpha",
"new_version": "1.7.73-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.73-alpha/archipelago-frontend-1.7.73-alpha.tar.gz",
"sha256": "586ee104787b8c69230f2616dd6256926299907fde53d6dd6f0015b98c5db41b",
"size_bytes": 166488057
"name": "archipelago-frontend-1.7.72-alpha.tar.gz",
"current_version": "1.7.72-alpha",
"new_version": "1.7.72-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.72-alpha/archipelago-frontend-1.7.72-alpha.tar.gz",
"sha256": "26e26be4f804685694e5885cf4434ae0ecf0d24c4a5cebd32a81239be1517789",
"size_bytes": 166481709
}
]
}