feat: wire CMD-K spotlight search to installed apps

Dynamically builds searchable items from installed packages so typing
an app name in CMD-K finds and launches it via the app launcher overlay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-05 07:27:43 +00:00
parent 064da257da
commit 70bc71d035
2 changed files with 49 additions and 8 deletions

View File

@ -59,7 +59,7 @@ After getting Claude Max OAuth working on the live server, hardening the deploy
## Phase 2: Service Wiring & Search (Tasks 10-16) — ~2.5 hours
### Task 10: CMD-K app search and launch
### Task 10: CMD-K app search and launch [DONE]
- **Files**: `neode-ui/src/components/SpotlightSearch.vue`
- **Change**: Import `useAppStore` and `useAppLauncherStore`. Create computed `dynamicAppItems` from `store.packages`. Merge with static help tree items in Fuse search. When app item selected, call `launchApp()`.
- **Verify**: CMD+K, type app name, appears in results, click to launch

View File

@ -115,11 +115,15 @@ import { useRouter } from 'vue-router'
import Fuse from 'fuse.js'
import { useSpotlightStore } from '@/stores/spotlight'
import { useCLIStore } from '@/stores/cli'
import { useAppStore } from '@/stores/app'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { helpTree, flattenForSearch, type SearchableItem } from '@/data/helpTree'
const router = useRouter()
const spotlightStore = useSpotlightStore()
const cliStore = useCLIStore()
const appStore = useAppStore()
const appLauncherStore = useAppLauncherStore()
const inputRef = ref<HTMLInputElement | null>(null)
const panelRef = ref<HTMLElement | null>(null)
@ -129,16 +133,31 @@ const query = ref('')
const isDragging = ref(false)
const dragStart = ref<{ x: number; y: number; panelX: number; panelY: number } | null>(null)
const searchableItems = flattenForSearch()
const fuse = new Fuse(searchableItems, {
const staticItems = flattenForSearch()
// Build dynamic app items from installed packages
const dynamicAppItems = computed<SearchableItem[]>(() => {
const pkgs = appStore.packages
return Object.entries(pkgs).map(([id, pkg]) => ({
id: `app-${id}`,
label: pkg.manifest?.title || id,
path: `__launch_app__:${id}`,
type: 'action' as const,
section: 'Installed Apps',
}))
})
const allSearchableItems = computed(() => [...staticItems, ...dynamicAppItems.value])
const fuse = computed(() => new Fuse(allSearchableItems.value, {
keys: ['label', 'section'],
threshold: 0.4,
})
}))
const filteredItems = computed(() => {
const q = query.value.trim()
if (!q) return []
const results = fuse.search(q)
const results = fuse.value.search(q)
return results.map((r) => r.item)
})
@ -148,7 +167,7 @@ const recentOffset = computed(() =>
const selectableCount = computed(() => {
if (query.value.trim()) return filteredItems.value.length
return recentOffset.value + searchableItems.length
return recentOffset.value + allSearchableItems.value.length
})
const panelStyle = computed(() => {
@ -202,6 +221,20 @@ function getItemClass(index: number) {
: 'hover:bg-white/10 text-white/90'
}
function launchInstalledApp(appId: string) {
const pkg = appStore.packages[appId]
if (!pkg) return
let lanAddress = pkg.installed?.['interface-addresses']?.main?.['lan-address']
if (lanAddress && lanAddress.includes('localhost')) {
lanAddress = lanAddress.replace('localhost', window.location.hostname)
}
if (lanAddress) {
appLauncherStore.open({ url: lanAddress, title: pkg.manifest?.title || appId })
} else {
router.push(`/dashboard/apps/${appId}`).catch(() => {})
}
}
function selectItem(item: SearchableItem) {
spotlightStore.addRecentItem({
id: item.id,
@ -210,7 +243,9 @@ function selectItem(item: SearchableItem) {
type: item.type,
})
spotlightStore.close()
if (item.path === '__cli__') {
if (item.path?.startsWith('__launch_app__:')) {
launchInstalledApp(item.path.replace('__launch_app__:', ''))
} else if (item.path === '__cli__') {
cliStore.open()
} else if (item.path) {
router.push(item.path)
@ -228,7 +263,9 @@ function selectHelpItem(section: { id: string }, item: { id: string; label: stri
type,
})
spotlightStore.close()
if (item.path === '__cli__') {
if (item.path?.startsWith('__launch_app__:')) {
launchInstalledApp(item.path.replace('__launch_app__:', ''))
} else if (item.path === '__cli__') {
cliStore.open()
} else if (item.path) {
router.push(item.path)
@ -239,6 +276,10 @@ function selectHelpItem(section: { id: string }, item: { id: string; label: stri
function selectRecent(item: { id: string; label: string; path?: string; type: 'navigate' | 'learn' | 'action' | 'goal' }) {
spotlightStore.close()
if (item.path?.startsWith('__launch_app__:')) {
launchInstalledApp(item.path.replace('__launch_app__:', ''))
return
}
if (item.path === '__cli__') {
cliStore.open()
return