2026-02-17 15:03:34 +00:00
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
import { ref, reactive } from 'vue'
|
2026-02-18 10:35:04 +00:00
|
|
|
import { playNavSound } from '@/composables/useNavSounds'
|
2026-02-17 15:03:34 +00:00
|
|
|
|
|
|
|
|
const RECENT_ITEMS_KEY = 'archipelago-spotlight-recent'
|
|
|
|
|
const MAX_RECENT_ITEMS = 8
|
|
|
|
|
|
|
|
|
|
export interface RecentItem {
|
|
|
|
|
id: string
|
|
|
|
|
label: string
|
|
|
|
|
path?: string
|
2026-03-04 07:09:31 +00:00
|
|
|
type: 'navigate' | 'learn' | 'action' | 'goal'
|
2026-02-17 15:03:34 +00:00
|
|
|
timestamp: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useSpotlightStore = defineStore('spotlight', () => {
|
|
|
|
|
const isOpen = ref(false)
|
|
|
|
|
const selectedIndex = ref(0)
|
|
|
|
|
|
|
|
|
|
const recentItems = ref<RecentItem[]>([])
|
|
|
|
|
|
|
|
|
|
function loadRecentItems() {
|
|
|
|
|
try {
|
|
|
|
|
const raw = localStorage.getItem(RECENT_ITEMS_KEY)
|
|
|
|
|
if (raw) {
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
const parsed: unknown = JSON.parse(raw)
|
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
|
|
|
recentItems.value = []
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
recentItems.value = parsed
|
|
|
|
|
.filter((item: unknown) =>
|
|
|
|
|
typeof item === 'object' && item !== null &&
|
|
|
|
|
'id' in item && 'label' in item && 'type' in item && 'timestamp' in item
|
|
|
|
|
)
|
|
|
|
|
.slice(0, MAX_RECENT_ITEMS) as RecentItem[]
|
2026-02-17 15:03:34 +00:00
|
|
|
} else {
|
|
|
|
|
recentItems.value = []
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
recentItems.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addRecentItem(item: Omit<RecentItem, 'timestamp'>) {
|
|
|
|
|
const withTimestamp: RecentItem = { ...item, timestamp: Date.now() }
|
|
|
|
|
const filtered = recentItems.value.filter(
|
|
|
|
|
(r) => !(r.id === item.id && r.type === item.type)
|
|
|
|
|
)
|
|
|
|
|
recentItems.value = [withTimestamp, ...filtered].slice(0, MAX_RECENT_ITEMS)
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem(RECENT_ITEMS_KEY, JSON.stringify(recentItems.value))
|
2026-03-11 00:58:55 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
if (import.meta.env.DEV) console.warn('Failed to save recent items to storage', e)
|
2026-02-17 15:03:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function open() {
|
|
|
|
|
isOpen.value = true
|
|
|
|
|
selectedIndex.value = 0
|
|
|
|
|
loadRecentItems()
|
2026-02-18 10:35:04 +00:00
|
|
|
playNavSound('action')
|
2026-02-17 15:03:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function close() {
|
|
|
|
|
isOpen.value = false
|
|
|
|
|
selectedIndex.value = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggle() {
|
|
|
|
|
isOpen.value ? close() : open()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setSelectedIndex(index: number) {
|
|
|
|
|
selectedIndex.value = index
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const helpModal = reactive({
|
|
|
|
|
show: false,
|
|
|
|
|
title: '',
|
|
|
|
|
content: '',
|
|
|
|
|
relatedPath: undefined as string | undefined,
|
|
|
|
|
})
|
2026-02-17 22:10:38 +00:00
|
|
|
const helpModalRestoreFocusRef = ref<HTMLElement | null>(null)
|
2026-02-17 15:03:34 +00:00
|
|
|
|
|
|
|
|
function showHelpModal(payload: { title: string; content: string; relatedPath?: string }) {
|
2026-02-17 22:10:38 +00:00
|
|
|
helpModalRestoreFocusRef.value = document.activeElement as HTMLElement | null
|
2026-02-17 15:03:34 +00:00
|
|
|
helpModal.show = true
|
|
|
|
|
helpModal.title = payload.title
|
|
|
|
|
helpModal.content = payload.content
|
|
|
|
|
helpModal.relatedPath = payload.relatedPath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeHelpModal() {
|
2026-02-17 22:10:38 +00:00
|
|
|
helpModalRestoreFocusRef.value?.focus?.()
|
|
|
|
|
helpModalRestoreFocusRef.value = null
|
2026-02-17 15:03:34 +00:00
|
|
|
helpModal.show = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
isOpen,
|
|
|
|
|
selectedIndex,
|
|
|
|
|
recentItems,
|
|
|
|
|
open,
|
|
|
|
|
close,
|
|
|
|
|
toggle,
|
|
|
|
|
setSelectedIndex,
|
|
|
|
|
addRecentItem,
|
|
|
|
|
loadRecentItems,
|
|
|
|
|
helpModal,
|
|
|
|
|
showHelpModal,
|
|
|
|
|
closeHelpModal,
|
|
|
|
|
}
|
|
|
|
|
})
|