Merge branch 'iso-feedback-fixes-2026-07-02' into merge-iso-feedback
# Conflicts: # core/archipelago/src/api/rpc/middleware.rs
This commit is contained in:
@@ -397,6 +397,7 @@ import { useAppsActions } from './apps/useAppsActions'
|
||||
import { validateSideloadRequest } from './apps/sideloadValidation'
|
||||
import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||
import { useCollapsingHeaderTabs } from '@/composables/useCollapsingHeaderTabs'
|
||||
import { useContainersScanTimeout } from '@/composables/useContainersScanTimeout'
|
||||
import {
|
||||
type AppsTab, filterEntriesForTab, isWebOnlyApp, isWebsitePackage, opensInTab, resolveRuntimeLaunchUrl,
|
||||
WEB_ONLY_APPS, WEB_ONLY_APP_URLS, buildAllCategories, useCategoriesWithApps,
|
||||
@@ -461,7 +462,14 @@ const ALL_CATEGORIES = computed(() => buildAllCategories(t))
|
||||
const SERVICE_CATEGORIES = computed(() => buildServiceCategories(t))
|
||||
|
||||
const livePackages = computed(() => store.packages || {})
|
||||
const containersScanned = computed(() => store.data?.['server-info']?.['status-info']?.['containers-scanned'] !== false)
|
||||
// Field missing from server data = not scanned yet (consistent with Discover/Marketplace)
|
||||
const containersScannedRaw = computed(() => store.data?.['server-info']?.['status-info']?.['containers-scanned'] ?? false)
|
||||
// Escape hatch: never show "Checking containers…" forever — after a timeout,
|
||||
// fall through to the real (empty) state even if the scanned flag never arrives.
|
||||
const { effectiveContainersScanned: containersScanned } = useContainersScanTimeout(
|
||||
containersScannedRaw,
|
||||
computed(() => store.hasLoadedInitialData),
|
||||
)
|
||||
const {
|
||||
packages: stablePackages,
|
||||
isUsingLastKnownPackages,
|
||||
|
||||
@@ -178,12 +178,12 @@ const backgroundImage = computed(() => {
|
||||
if (mapped) return mapped
|
||||
// Detail/sub pages inherit their parent tab's background so they stay
|
||||
// visually "inside" the section instead of snapping to the home backdrop.
|
||||
if (route.path.startsWith('/dashboard/cloud/')) return 'bg-cloud.jpg'
|
||||
if (route.path.startsWith('/dashboard/cloud/')) return 'bg-cloud.webp'
|
||||
if (route.path.startsWith('/dashboard/web5/')) return 'bg-web5.jpg'
|
||||
if (route.path.startsWith('/dashboard/server/')) return 'bg-web5.jpg'
|
||||
if (route.path.startsWith('/dashboard/settings/')) return 'bg-settings.jpg'
|
||||
if (route.path.startsWith('/dashboard/settings/')) return 'bg-settings.webp'
|
||||
if (isDetailRoute(route.path)) return 'bg-intro.jpg'
|
||||
return 'bg-home.jpg'
|
||||
return 'bg-home.webp'
|
||||
})
|
||||
|
||||
const isDarkRoute = computed(() => {
|
||||
|
||||
@@ -235,6 +235,7 @@ import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||
import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { useCollapsingHeaderTabs } from '@/composables/useCollapsingHeaderTabs'
|
||||
import { useContainersScanTimeout } from '@/composables/useContainersScanTimeout'
|
||||
import { APP_STORE_SECTIONS } from './appStoreCategories'
|
||||
import DiscoverHero from './discover/DiscoverHero.vue'
|
||||
import FeaturedApps from './discover/FeaturedApps.vue'
|
||||
@@ -335,7 +336,13 @@ function retryNostr() {
|
||||
}
|
||||
|
||||
const installedPackages = computed(() => store.data?.['package-data'] || {})
|
||||
const containersScanned = computed(() => store.data?.['server-info']?.['status-info']?.['containers-scanned'] ?? false)
|
||||
const containersScannedRaw = computed(() => store.data?.['server-info']?.['status-info']?.['containers-scanned'] ?? false)
|
||||
// Escape hatch: never leave app cards on "Checking..." forever — after a
|
||||
// timeout, treat the scan as done so cards render their normal install state.
|
||||
const { effectiveContainersScanned: containersScanned } = useContainersScanTimeout(
|
||||
containersScannedRaw,
|
||||
computed(() => store.hasLoadedInitialData),
|
||||
)
|
||||
|
||||
|
||||
const allApps = computed(() => {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
data-form-type="other"
|
||||
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
||||
:placeholder="t('login.enterPasswordSetup')"
|
||||
@keydown.enter="handleSetupWithSound"
|
||||
@keydown.enter="confirmPasswordInputRef?.focus()"
|
||||
@input="error = null"
|
||||
:disabled="loading || formDisabled"
|
||||
/>
|
||||
</div>
|
||||
@@ -77,6 +78,7 @@
|
||||
</label>
|
||||
<input
|
||||
id="setup-confirm-password"
|
||||
ref="confirmPasswordInputRef"
|
||||
v-model="confirmPassword"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
@@ -84,14 +86,16 @@
|
||||
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
||||
:placeholder="t('login.confirmPasswordPlaceholder')"
|
||||
@keydown.enter="handleSetupWithSound"
|
||||
@input="error = null"
|
||||
:disabled="loading || formDisabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="handleSetupWithSound"
|
||||
:disabled="loading || formDisabled || !password || password.length < 8 || password !== confirmPassword"
|
||||
:disabled="loading || formDisabled"
|
||||
class="w-full glass-button px-6 py-3 rounded-lg font-medium transition-all hover:bg-black/70 hover:border-white/30 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:class="{ 'opacity-60': !password || password.length < 8 || password !== confirmPassword }"
|
||||
>
|
||||
<span v-if="!loading">{{ t('login.setupButton') }}</span>
|
||||
<span v-else class="flex items-center justify-center">
|
||||
@@ -213,9 +217,10 @@
|
||||
<button
|
||||
@click="restartOnboarding"
|
||||
:disabled="isResettingOnboarding"
|
||||
class="text-xs text-white/50 hover:text-white/70 transition-colors underline-offset-2 hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="text-xs transition-colors underline-offset-2 hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:class="confirmingRestartOnboarding ? 'text-orange-400 hover:text-orange-300' : 'text-white/50 hover:text-white/70'"
|
||||
>
|
||||
{{ isResettingOnboarding ? t('login.resetting') : t('login.onboarding') }}
|
||||
{{ isResettingOnboarding ? t('login.resetting') : (confirmingRestartOnboarding ? t('login.restartConfirm') : t('login.onboarding')) }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -260,6 +265,7 @@ const requiresTotp = ref(false)
|
||||
const totpCode = ref('')
|
||||
const useBackupCode = ref(false)
|
||||
const totpInputRef = ref<HTMLInputElement | null>(null)
|
||||
const confirmPasswordInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Server startup state
|
||||
const serverReady = ref(false)
|
||||
@@ -338,6 +344,7 @@ onBeforeUnmount(() => {
|
||||
removeUnlockListeners()
|
||||
if (startupPollTimer) clearTimeout(startupPollTimer)
|
||||
if (startupProgressInterval) clearInterval(startupProgressInterval)
|
||||
if (confirmRestartTimer) clearTimeout(confirmRestartTimer)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -536,9 +543,26 @@ function replayIntro() {
|
||||
}
|
||||
|
||||
const isResettingOnboarding = ref(false)
|
||||
const confirmingRestartOnboarding = ref(false)
|
||||
let confirmRestartTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
async function restartOnboarding() {
|
||||
if (isResettingOnboarding.value) return
|
||||
// First click arms a confirmation state; only a second explicit click restarts.
|
||||
if (!confirmingRestartOnboarding.value) {
|
||||
confirmingRestartOnboarding.value = true
|
||||
if (confirmRestartTimer) clearTimeout(confirmRestartTimer)
|
||||
confirmRestartTimer = setTimeout(() => {
|
||||
confirmingRestartOnboarding.value = false
|
||||
confirmRestartTimer = null
|
||||
}, 5000)
|
||||
return
|
||||
}
|
||||
if (confirmRestartTimer) {
|
||||
clearTimeout(confirmRestartTimer)
|
||||
confirmRestartTimer = null
|
||||
}
|
||||
confirmingRestartOnboarding.value = false
|
||||
isResettingOnboarding.value = true
|
||||
// Local-only reset — no RPC needed since user isn't logged in.
|
||||
// Onboarding pages are all public, so clearing localStorage is enough.
|
||||
|
||||
@@ -170,6 +170,7 @@ import { useMarketplaceApp } from '@/composables/useMarketplaceApp'
|
||||
import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { useCollapsingHeaderTabs } from '@/composables/useCollapsingHeaderTabs'
|
||||
import { useContainersScanTimeout } from '@/composables/useContainersScanTimeout'
|
||||
import { APP_STORE_CATEGORIES, APP_STORE_SECTIONS } from './appStoreCategories'
|
||||
import MarketplaceAppCard from './marketplace/MarketplaceAppCard.vue'
|
||||
import {
|
||||
@@ -286,9 +287,15 @@ const installedPackages = computed(() => {
|
||||
return store.data?.['package-data'] || {}
|
||||
})
|
||||
|
||||
const containersScanned = computed(() => {
|
||||
const containersScannedRaw = computed(() => {
|
||||
return store.data?.['server-info']?.['status-info']?.['containers-scanned'] ?? false
|
||||
})
|
||||
// Escape hatch: never leave app cards on "Checking..." forever — after a
|
||||
// timeout, treat the scan as done so cards render their normal install state.
|
||||
const { effectiveContainersScanned: containersScanned } = useContainersScanTimeout(
|
||||
containersScannedRaw,
|
||||
computed(() => store.hasLoadedInitialData),
|
||||
)
|
||||
|
||||
// Combine curated apps with Nostr relay-discovered apps
|
||||
const allApps = computed(() => {
|
||||
|
||||
@@ -109,16 +109,16 @@ const useVideoBackground = computed(() => {
|
||||
// Note: bg-intro.jpg is used for splash and /onboarding/intro for seamless transition
|
||||
const routeBackgrounds: Record<string, string> = {
|
||||
'/onboarding/intro': 'bg-intro.jpg', // Video will be used instead
|
||||
'/onboarding/options': 'bg-intro-4.jpg',
|
||||
'/onboarding/options': 'bg-intro-4.webp',
|
||||
'/onboarding/path': 'bg-intro-3.jpg',
|
||||
'/onboarding/seed': 'bg-intro-1.jpg',
|
||||
'/onboarding/seed-verify': 'bg-intro-1.jpg',
|
||||
'/onboarding/seed-restore': 'bg-intro-1.jpg',
|
||||
'/onboarding/did': 'bg-intro-4.jpg',
|
||||
'/onboarding/identity': 'bg-intro-1.jpg',
|
||||
'/onboarding/backup': 'bg-intro-6.jpg',
|
||||
'/onboarding/seed': 'bg-intro-1.webp',
|
||||
'/onboarding/seed-verify': 'bg-intro-1.webp',
|
||||
'/onboarding/seed-restore': 'bg-intro-1.webp',
|
||||
'/onboarding/did': 'bg-intro-4.webp',
|
||||
'/onboarding/identity': 'bg-intro-1.webp',
|
||||
'/onboarding/backup': 'bg-intro-6.webp',
|
||||
'/onboarding/verify': 'bg-intro-2.jpg',
|
||||
'/onboarding/done': 'bg-intro-1.jpg',
|
||||
'/onboarding/done': 'bg-intro-1.webp',
|
||||
'/login': 'bg-intro.jpg' // Video loops from splash (same as intro)
|
||||
}
|
||||
|
||||
@@ -126,12 +126,12 @@ const routeBackgrounds: Record<string, string> = {
|
||||
// identical on every logout. Cycles through bg-intro-1..6 using a
|
||||
// counter persisted to localStorage so subsequent visits advance.
|
||||
const LOGIN_BACKGROUNDS = [
|
||||
'bg-intro-1.jpg',
|
||||
'bg-intro-1.webp',
|
||||
'bg-intro-2.jpg',
|
||||
'bg-intro-3.jpg',
|
||||
'bg-intro-4.jpg',
|
||||
'bg-intro-5.jpg',
|
||||
'bg-intro-6.jpg',
|
||||
'bg-intro-4.webp',
|
||||
'bg-intro-5.webp',
|
||||
'bg-intro-6.webp',
|
||||
]
|
||||
function pickNextLoginBackground(): string {
|
||||
try {
|
||||
@@ -307,7 +307,7 @@ watch(() => route.path, (newPath, oldPath) => {
|
||||
|
||||
// Login route: set background immediately, no zoom, no transition (glitch is always-on)
|
||||
if (newPath === '/login') {
|
||||
currentBackground.value = 'bg-intro-1.jpg'
|
||||
currentBackground.value = 'bg-intro-1.webp'
|
||||
isTransitioning.value = false
|
||||
isGlitching.value = false
|
||||
return
|
||||
|
||||
@@ -19,19 +19,19 @@ const WEB5_TAB_ORDER = ['/dashboard/web5', '/dashboard/cloud', '/dashboard/serve
|
||||
|
||||
/** Route-to-background image mapping */
|
||||
export const ROUTE_BACKGROUNDS: Record<string, string> = {
|
||||
'/dashboard': 'bg-home.jpg',
|
||||
'/dashboard/': 'bg-home.jpg',
|
||||
'/dashboard/apps': 'bg-myapps.jpg',
|
||||
'/dashboard/discover': 'bg-appstore.jpg',
|
||||
'/dashboard/marketplace': 'bg-appstore.jpg',
|
||||
'/dashboard/cloud': 'bg-cloud.jpg',
|
||||
'/dashboard/mesh': 'bg-mesh.jpg',
|
||||
'/dashboard': 'bg-home.webp',
|
||||
'/dashboard/': 'bg-home.webp',
|
||||
'/dashboard/apps': 'bg-myapps.webp',
|
||||
'/dashboard/discover': 'bg-appstore.webp',
|
||||
'/dashboard/marketplace': 'bg-appstore.webp',
|
||||
'/dashboard/cloud': 'bg-cloud.webp',
|
||||
'/dashboard/mesh': 'bg-mesh.webp',
|
||||
'/dashboard/server': 'bg-network.jpg',
|
||||
'/dashboard/web5': 'bg-web5.jpg',
|
||||
'/dashboard/server/federation': 'bg-web5.jpg',
|
||||
'/dashboard/monitoring': 'bg-web5.jpg',
|
||||
'/dashboard/fleet': 'bg-web5.jpg',
|
||||
'/dashboard/settings': 'bg-settings.jpg',
|
||||
'/dashboard/settings': 'bg-settings.webp',
|
||||
'/dashboard/chat': 'bg-aiui.jpg',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user