feat(neode-ui): mobile app-launch UX — store-driven panel, loader, ElectrumX icon

- Mobile launches use the store-driven panel (no route push) so the background
  tab no longer changes and closing returns to where you launched from.
- Tab-only apps open directly (in-app WebView on companion / new tab on PWA) —
  no "this app opens in a tab" interstitial.
- Shared AppLoadingScreen (app icon + progress bar) on the app session and the
  legacy iframe overlay instead of a black screen.
- Pin the dashboard to 100dvh on mobile so the mesh chat/tools panes stop sliding
  under the bottom tab bar in mobile browsers (no-op in the companion WebView).
- ElectrumX/electrs/electrs-ui ids now resolve to the real ElectrumX icon in My Apps.
- isMobile made reactive so overlay/footer/teleport decisions track the viewport.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-23 03:48:57 -04:00
co-authored by Claude Opus 4.8
parent db7d424bff
commit a7c7c44843
11 changed files with 387 additions and 56 deletions
@@ -3,8 +3,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import AppSession from '../AppSession.vue'
const { mockReplace, mockPush, mockWindowOpen, mockSuppress, mockResume } = vi.hoisted(() => ({
mockReplace: vi.fn(),
mockPush: vi.fn(),
mockReplace: vi.fn(() => Promise.resolve()),
mockPush: vi.fn(() => Promise.resolve()),
mockWindowOpen: vi.fn(),
mockSuppress: vi.fn(),
mockResume: vi.fn(),
@@ -62,7 +62,7 @@ describe('AppSession mobile new-tab apps', () => {
})
})
it('keeps iframe-blocked apps inside the mobile session instead of auto-opening a tab', async () => {
it('opens tab-only apps directly on mobile instead of showing an interstitial', async () => {
const wrapper = mount(AppSession, {
global: {
stubs: {
@@ -75,9 +75,11 @@ describe('AppSession mobile new-tab apps', () => {
})
await flushPromises()
expect(mockWindowOpen).not.toHaveBeenCalled()
expect(mockReplace).not.toHaveBeenCalled()
expect(wrapper.text()).toContain('This app opens in a new tab')
expect(wrapper.text()).toContain('Open in new tab')
// Tab-only app (gitea) on mobile-web: open directly in a new browser tab
// (no native bridge in the test) and dismiss the empty session — no
// "this app opens in a tab" interstitial.
expect(mockWindowOpen).toHaveBeenCalled()
expect(mockReplace).toHaveBeenCalled()
expect(wrapper.text()).not.toContain('This app opens in a new tab')
})
})