fix(demo-ui): demoable apps launch in-frame; 'Not available in demo' label

- Register the 8 new placeholder apps in DEMO_MOCK_UI so they're launchable
  and installable in the demo.
- Demo bypasses the new-tab/tab-launch lists for demoable apps — they're
  served same-origin by the mock backend with no framing headers, so they
  render in the in-app session instead of opening broken localhost tabs.
- Non-demoable apps now say 'Not available in demo' instead of 'No demo'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-07-14 03:39:56 +01:00
parent 7d7a7d08af
commit 4135ee5f4a
5 changed files with 18 additions and 6 deletions

View File

@ -75,6 +75,15 @@ const DEMO_MOCK_UI: Record<string, string> = {
fedimint: '/app/fedimint/',
fedimintd: '/app/fedimint/',
filebrowser: '/app/filebrowser/',
// Static placeholder dashboards served by the mock backend (DEMO_APP_PAGES).
'btcpay-server': '/app/btcpay-server/',
grafana: '/app/grafana/',
nextcloud: '/app/nextcloud/',
jellyfin: '/app/jellyfin/',
vaultwarden: '/app/vaultwarden/',
'nostr-rs-relay': '/app/nostr-rs-relay/',
searxng: '/app/searxng/',
'uptime-kuma': '/app/uptime-kuma/',
}
/**

View File

@ -404,6 +404,7 @@ import {
buildServiceCategories, useServiceCategories,
} from './apps/appsConfig'
import { getCuratedAppList, INSTALLED_ALIASES, type MarketplaceApp } from './marketplace/marketplaceData'
import { IS_DEMO, isDemoApp } from '@/composables/useDemoIntro'
const { t } = useI18n()
const router = useRouter()
@ -633,7 +634,9 @@ function launchAppNow(id: string) {
}
return
}
if (!isMobile && pkg && opensInTab(id)) {
// Demo: demoable apps are served same-origin by the mock backend, so the
// tab-launch list (real apps with framing headers) doesn't apply.
if (!isMobile && pkg && opensInTab(id) && !(IS_DEMO && isDemoApp(id))) {
const url = resolveRuntimeLaunchUrl(pkg)
if (url) {
window.open(url, '_blank', 'noopener,noreferrer')

View File

@ -85,7 +85,7 @@
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
{{ demoNoInstall ? 'No demo' : installBlockedReason ? 'Bitcoin Pruned' : installing ? t('common.installing') : t('common.install') }}
{{ demoNoInstall ? 'Not available in demo' : installBlockedReason ? 'Bitcoin Pruned' : installing ? t('common.installing') : t('common.install') }}
</button>
</div>
</div>
@ -164,7 +164,7 @@
<svg v-else class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
{{ demoNoInstall ? 'No demo' : installBlockedReason ? 'Bitcoin Pruned' : installing ? t('common.installing') : t('common.install') }}
{{ demoNoInstall ? 'Not available in demo' : installBlockedReason ? 'Bitcoin Pruned' : installing ? t('common.installing') : t('common.install') }}
</button>
</div>
@ -517,7 +517,7 @@ const installBlockedReason = computed(() => {
return electrumxArchiveWarning
})
// Demo: only demoable apps can be installed; the rest show "No demo".
// Demo: only demoable apps can be installed; the rest show "Not available in demo".
const demoNoInstall = computed(() => IS_DEMO && !!app.value?.id && !isDemoApp(app.value.id))
let pendingRedirect: ReturnType<typeof setTimeout> | null = null

View File

@ -121,7 +121,7 @@
v-else-if="IS_DEMO && !isInstalled(app.id) && !isDemoApp(app.id)"
disabled
class="flex-1 px-4 py-2 bg-white/10 rounded-lg text-white/40 text-sm font-medium cursor-not-allowed"
>No demo</button>
>Not available in demo</button>
<!-- Install button -->
<button
v-else-if="!isInstalled(app.id) && (app.source === 'local' || app.dockerImage)"

View File

@ -78,7 +78,7 @@
v-else-if="IS_DEMO && !isInstalled(app.id) && !isDemoApp(app.id)"
disabled
class="glass-button glass-button-sm rounded-lg text-sm font-medium opacity-50 cursor-not-allowed"
>No demo</button>
>Not available in demo</button>
<button
v-else-if="!isInstalled(app.id) && app.dockerImage"
data-controller-install-btn