feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking: - E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes relay encrypted blobs transparently via Meshcore native routing - Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic looks like sensor data on the wire, 0xAA marker, configurable per-node - Pre-flight Bitcoin Core health check on relay node — specific error codes (bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails - mesh.relay-status RPC endpoint — frontend polls for relay result every 3s - On-Chain / Lightning tabs in Off-Grid Bitcoin panel - Archy Peers vs Mesh Broadcast relay mode selector - Mesh view fills viewport (no page scroll), internal panel scrolling - Version bump to 1.2.0-alpha Also includes: deploy hardening, container fixes, IndeedHub updates, boot screen, dashboard improvements, MASTER_PLAN task tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
70f1348c15
commit
d37ec1dea5
@@ -189,38 +189,22 @@
|
||||
:class="{ 'glass-throw-mobile-tabs': showZoomIn }"
|
||||
style="background: rgba(0, 0, 0, 0.25); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); transform: translateZ(0);"
|
||||
>
|
||||
<div class="glass-card p-2 rounded-lg flex gap-2 relative">
|
||||
<!-- Animated Active Indicator -->
|
||||
<div
|
||||
class="absolute top-2 bottom-2 rounded-lg bg-white/20 transition-all duration-300 ease-out"
|
||||
:style="{
|
||||
left: `${appsTabIndicatorLeft}px`,
|
||||
width: `${appsTabIndicatorWidth}px`,
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div class="mode-switcher mode-switcher-full">
|
||||
<RouterLink
|
||||
ref="appsTabRef"
|
||||
to="/dashboard/apps"
|
||||
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-300 text-center relative z-10"
|
||||
:class="{
|
||||
'bg-white/20 text-white': route.path === '/dashboard/apps' || route.path.startsWith('/dashboard/apps/'),
|
||||
'text-white/60 hover:text-white/80': !(route.path === '/dashboard/apps' || route.path.startsWith('/dashboard/apps/'))
|
||||
}"
|
||||
>
|
||||
My Apps
|
||||
</RouterLink>
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': (route.path === '/dashboard/apps' || route.path.startsWith('/dashboard/apps/')) && route.query.tab !== 'services' }"
|
||||
>My Apps</RouterLink>
|
||||
<RouterLink
|
||||
ref="marketplaceTabRef"
|
||||
to="/dashboard/marketplace"
|
||||
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-300 text-center relative z-10"
|
||||
:class="{
|
||||
'bg-white/20 text-white': route.path === '/dashboard/marketplace' || route.path.startsWith('/dashboard/marketplace/'),
|
||||
'text-white/60 hover:text-white/80': !(route.path === '/dashboard/marketplace' || route.path.startsWith('/dashboard/marketplace/'))
|
||||
}"
|
||||
>
|
||||
App Store
|
||||
</RouterLink>
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.path === '/dashboard/marketplace' || route.path.startsWith('/dashboard/marketplace/') }"
|
||||
>App Store</RouterLink>
|
||||
<RouterLink
|
||||
to="/dashboard/apps?tab=services"
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.query.tab === 'services' }"
|
||||
>Services</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,38 +216,27 @@
|
||||
style="background: rgba(0, 0, 0, 0.25); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); transform: translateZ(0);"
|
||||
:style="{ top: showAppsTabs ? '80px' : '0' }"
|
||||
>
|
||||
<div class="glass-card p-2 rounded-lg flex gap-2 relative">
|
||||
<!-- Animated Active Indicator -->
|
||||
<div
|
||||
class="absolute top-2 bottom-2 rounded-lg bg-white/20 transition-all duration-300 ease-out"
|
||||
:style="{
|
||||
left: `${networkTabIndicatorLeft}px`,
|
||||
width: `${networkTabIndicatorWidth}px`,
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div class="mode-switcher mode-switcher-full">
|
||||
<RouterLink
|
||||
to="/dashboard/web5"
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.path === '/dashboard/web5' || route.path.startsWith('/dashboard/web5/') }"
|
||||
>Web5</RouterLink>
|
||||
<RouterLink
|
||||
ref="cloudTabRef"
|
||||
to="/dashboard/cloud"
|
||||
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-300 text-center relative z-10"
|
||||
:class="{
|
||||
'bg-white/20 text-white': route.path === '/dashboard/cloud' || route.path.startsWith('/dashboard/cloud/'),
|
||||
'text-white/60 hover:text-white/80': !(route.path === '/dashboard/cloud' || route.path.startsWith('/dashboard/cloud/'))
|
||||
}"
|
||||
>
|
||||
Cloud
|
||||
</RouterLink>
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.path === '/dashboard/cloud' || route.path.startsWith('/dashboard/cloud/') }"
|
||||
>Cloud</RouterLink>
|
||||
<RouterLink
|
||||
ref="serverTabRef"
|
||||
to="/dashboard/server"
|
||||
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-300 text-center relative z-10"
|
||||
:class="{
|
||||
'bg-white/20 text-white': route.path === '/dashboard/server' || route.path.startsWith('/dashboard/server/'),
|
||||
'text-white/60 hover:text-white/80': !(route.path === '/dashboard/server' || route.path.startsWith('/dashboard/server/'))
|
||||
}"
|
||||
>
|
||||
Network
|
||||
</RouterLink>
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.path === '/dashboard/server' || route.path.startsWith('/dashboard/server/') }"
|
||||
>Network</RouterLink>
|
||||
<RouterLink
|
||||
to="/dashboard/mesh"
|
||||
class="mode-switcher-btn"
|
||||
:class="{ 'mode-switcher-btn-active': route.path === '/dashboard/mesh' || route.path.startsWith('/dashboard/mesh/') }"
|
||||
>Mesh</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -273,7 +246,7 @@
|
||||
<Transition :name="getTransitionName(route)">
|
||||
<div :key="route.path" class="view-wrapper">
|
||||
<div
|
||||
v-if="route.path === '/dashboard/chat'"
|
||||
v-if="route.path === '/dashboard/chat' || route.path === '/dashboard/mesh'"
|
||||
class="h-full"
|
||||
>
|
||||
<component :is="Component" />
|
||||
@@ -524,7 +497,7 @@ const showNetworkTabs = computed(() => {
|
||||
if (typeof window === 'undefined') return false
|
||||
if (window.innerWidth >= 768) return false
|
||||
if (route.name === 'cloud-folder') return false
|
||||
return route.path.includes('/server') || route.path.includes('/cloud')
|
||||
return route.path.includes('/server') || route.path.includes('/cloud') || route.path.includes('/web5') || route.path.includes('/mesh')
|
||||
})
|
||||
|
||||
// Top padding for content div to clear fixed mobile tab overlays
|
||||
@@ -700,7 +673,7 @@ const desktopNavItems = computed(() => {
|
||||
const gamerMobileNav: NavItem[] = [
|
||||
{ path: '/dashboard', label: 'Home', icon: 'home' },
|
||||
{ path: '/dashboard/apps', label: 'Apps', icon: 'apps', isCombined: true },
|
||||
{ path: '/dashboard/cloud', label: 'Network', icon: 'server', isCombined: true },
|
||||
{ path: '/dashboard/web5', label: 'Web5', icon: 'web5', isCombined: true },
|
||||
{ path: '/dashboard/settings', label: 'Settings', icon: 'settings' },
|
||||
]
|
||||
|
||||
@@ -754,6 +727,7 @@ async function handleLogout() {
|
||||
|
||||
// Track previous route for transition logic
|
||||
let previousPath = ''
|
||||
let previousTab = ''
|
||||
|
||||
// Tab order for vertical transitions
|
||||
const tabOrder = [
|
||||
@@ -809,38 +783,38 @@ function getTransitionName(currentRoute: RouteLocationNormalizedLoaded) {
|
||||
|
||||
let transitionName = 'fade'
|
||||
|
||||
// Mobile: Horizontal slide transitions between Apps and Marketplace (check first on mobile)
|
||||
// Mobile: Horizontal slide transitions between sub-tabs
|
||||
if (typeof window !== 'undefined' && window.innerWidth < 768) {
|
||||
// From Marketplace to Apps: slide right
|
||||
if (wasMarketplaceList && isAppsList) {
|
||||
transitionName = 'slide-right'
|
||||
// Apps group: My Apps (0) → App Store (1) → Services (2)
|
||||
const isServices = currentPath === '/dashboard/apps' && currentRoute.query.tab === 'services'
|
||||
const wasServices = previousTab === 'services'
|
||||
const currentAppsIdx = isServices ? 2
|
||||
: currentPath === '/dashboard/marketplace' ? 1
|
||||
: currentPath === '/dashboard/apps' ? 0 : -1
|
||||
const prevAppsIdx = wasServices ? 2
|
||||
: previousPath === '/dashboard/marketplace' ? 1
|
||||
: previousPath === '/dashboard/apps' ? 0 : -1
|
||||
|
||||
// Web5 group: Web5 (0) → Cloud (1) → Network (2) → Mesh (3)
|
||||
const web5TabOrder = ['/dashboard/web5', '/dashboard/cloud', '/dashboard/server', '/dashboard/mesh']
|
||||
const currentWeb5Idx = web5TabOrder.indexOf(currentPath)
|
||||
const prevWeb5Idx = web5TabOrder.indexOf(previousPath)
|
||||
|
||||
// Apps sub-tab transitions
|
||||
if (currentAppsIdx !== -1 && prevAppsIdx !== -1 && currentAppsIdx !== prevAppsIdx) {
|
||||
transitionName = currentAppsIdx > prevAppsIdx ? 'slide-left' : 'slide-right'
|
||||
}
|
||||
// From Apps to Marketplace: slide left
|
||||
else if (wasAppsList && isMarketplaceList) {
|
||||
transitionName = 'slide-left'
|
||||
// Web5 sub-tab transitions
|
||||
else if (currentWeb5Idx !== -1 && prevWeb5Idx !== -1 && currentWeb5Idx !== prevWeb5Idx) {
|
||||
transitionName = currentWeb5Idx > prevWeb5Idx ? 'slide-left' : 'slide-right'
|
||||
}
|
||||
// From Network to Cloud: slide right
|
||||
else if (previousPath === '/dashboard/server' && isCloudList) {
|
||||
transitionName = 'slide-right'
|
||||
}
|
||||
// From Cloud to Network: slide left
|
||||
else if (wasCloudList && currentPath === '/dashboard/server') {
|
||||
transitionName = 'slide-left'
|
||||
}
|
||||
// Vertical transition: between main tabs (mobile fallback)
|
||||
// Vertical transition: between main bottom nav tabs
|
||||
else {
|
||||
const currentIndex = tabOrder.indexOf(currentPath)
|
||||
const previousIndex = tabOrder.indexOf(previousPath)
|
||||
|
||||
|
||||
if (currentIndex !== -1 && previousIndex !== -1 && currentIndex !== previousIndex) {
|
||||
// Moving down the menu (visual down)
|
||||
if (currentIndex > previousIndex) {
|
||||
transitionName = 'slide-down'
|
||||
}
|
||||
// Moving up the menu (visual up)
|
||||
else {
|
||||
transitionName = 'slide-up'
|
||||
}
|
||||
transitionName = currentIndex > previousIndex ? 'slide-down' : 'slide-up'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -892,6 +866,7 @@ function getTransitionName(currentRoute: RouteLocationNormalizedLoaded) {
|
||||
|
||||
// Update previous path for next transition
|
||||
previousPath = currentPath
|
||||
previousTab = (currentRoute.query.tab as string) || ''
|
||||
|
||||
return transitionName
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user