Merge remote-tracking branch 'origin/main' into ark-merge

This commit is contained in:
Dorian
2026-07-14 22:08:55 +01:00
142 changed files with 7738 additions and 1336 deletions
@@ -262,12 +262,12 @@ describe('useAppLauncherStore', () => {
it('opens known prepackaged websites in new tab on desktop when requested', () => {
const store = useAppLauncherStore()
store.open({ url: 'https://nwnn.l484.com', title: 'Next Web News Network', openInNewTab: true })
store.open({ url: 'https://botfights.net', title: 'BotFights', openInNewTab: true })
expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith(
'https://nwnn.l484.com',
'https://botfights.net',
'_blank',
'noopener,noreferrer',
)
@@ -281,12 +281,12 @@ describe('useAppLauncherStore', () => {
})
const store = useAppLauncherStore()
store.open({ url: 'https://present.l484.com', title: 'Arch Presentation', openInNewTab: true })
store.open({ url: 'https://botfights.net', title: 'BotFights', openInNewTab: true })
// Iframeable prepackaged sites stay in-app via the store panel (no route
// change, no background swap) just like every other mobile launch.
expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe('arch-presentation')
expect(store.panelAppId).toBe('botfights')
expect(mockWindowOpen).not.toHaveBeenCalled()
expect(mockPush).not.toHaveBeenCalled()
})
-6
View File
@@ -328,12 +328,6 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
// Check external URLs
const EXTERNAL_APP_HOSTS: Record<string, string> = {
'botfights.net': 'botfights',
'nwnn.l484.com': 'nwnn',
'484.kitchen': '484-kitchen',
'cta.tx1138.com': 'call-the-operator',
'present.l484.com': 'arch-presentation',
'syntropy.institute': 'syntropy-institute',
'teeminuszero.net': 't-zero',
'nostrudel.ninja': 'nostrudel',
}
return EXTERNAL_APP_HOSTS[u.hostname] || null
+62 -2
View File
@@ -23,6 +23,31 @@ export interface MeshStatus {
receive_block_headers?: boolean
/** Operator-configured LoRa region (e.g. "EU_868"), for the Device tab. */
region?: string | null
/** Persisted config: LoRa region the driver programs on fresh radios. */
lora_region?: string | null
/** Persisted config: firmware pin (meshcore|meshtastic|reticulum), null = auto-detect. */
device_kind?: string | null
/** USB identity per detected port (for the setup modal's board image). */
detected_device_info?: Array<{
path: string
vid?: string | null
pid?: string | null
product?: string | null
manufacturer?: string | null
}>
}
/** Params accepted by mesh.configure (superset of the status fields). */
export interface MeshConfigureParams {
enabled?: boolean
device_path?: string
channel_name?: string
broadcast_identity?: boolean
advert_name?: string
announce_block_headers?: boolean
receive_block_headers?: boolean
lora_region?: string
device_kind?: string
}
export interface MeshPeer {
@@ -234,6 +259,38 @@ export const useMeshStore = defineStore('mesh', () => {
}
}
// ── Global device-detection (for the app-wide "device detected" modal) ──
// Ports the user dismissed the setup modal for, persisted per browser.
const DETECT_DISMISS_KEY = 'archipelago.mesh.detect-dismissed.v1'
const dismissedDetectedPaths = ref<Set<string>>(new Set(
JSON.parse(localStorage.getItem(DETECT_DISMISS_KEY) || '[]') as string[]
))
const undismissedDetectedDevices = computed(() => {
const s = status.value
if (!s || s.device_connected || s.enabled) return []
return (s.detected_devices || []).filter(p => !dismissedDetectedPaths.value.has(p))
})
function dismissDetectedDevice(path: string) {
dismissedDetectedPaths.value.add(path)
dismissedDetectedPaths.value = new Set(dismissedDetectedPaths.value)
localStorage.setItem(DETECT_DISMISS_KEY, JSON.stringify([...dismissedDetectedPaths.value]))
}
let globalDetectTimer: ReturnType<typeof setInterval> | null = null
/** App-wide light poll so the detected-device modal works on every page
* (the Mesh view's own 5s poll takes over while it is mounted). */
function startGlobalDetection(intervalMs = 15000) {
if (globalDetectTimer) return
// Never poll unauthenticated — a 401 from a background poll on the
// login page caused a login-refresh loop (2026-07-14, .116).
const authed = () => {
try { return !!localStorage.getItem('neode-auth') } catch { return false }
}
if (authed()) void fetchStatus()
globalDetectTimer = setInterval(() => {
if (document.visibilityState === 'visible' && authed()) void fetchStatus()
}, intervalMs)
}
async function fetchPeers() {
try {
const res = await rpcClient.call<{ peers: MeshPeer[]; count: number }>({
@@ -455,12 +512,12 @@ export const useMeshStore = defineStore('mesh', () => {
}
}
async function configure(config: Partial<MeshStatus>) {
async function configure(config: MeshConfigureParams) {
try {
error.value = null
await rpcClient.call<{ configured: boolean }>({
method: 'mesh.configure',
params: config,
params: config as Record<string, unknown>,
})
await fetchStatus()
} catch (err: unknown) {
@@ -851,6 +908,9 @@ export const useMeshStore = defineStore('mesh', () => {
blockHeaders,
latestBlockHeight,
fetchStatus,
undismissedDetectedDevices,
dismissDetectedDevice,
startGlobalDetection,
fetchPeers,
fetchMessages,
sendMessage,