From f8b9b0b30563f4d710a5a840f584ceebfeb95954 Mon Sep 17 00:00:00 2001 From: onmax Date: Sun, 1 Feb 2026 22:44:36 +0100 Subject: [PATCH 1/2] feat: project switching in command palette --- apps/shelve/app/components/CommandPalette.vue | 3 + apps/shelve/app/composables/useAppCommands.ts | 67 +++++++++++++++++++ packages/types/src/Command.ts | 1 + 3 files changed, 71 insertions(+) diff --git a/apps/shelve/app/components/CommandPalette.vue b/apps/shelve/app/components/CommandPalette.vue index 85279cb1..80e31070 100644 --- a/apps/shelve/app/components/CommandPalette.vue +++ b/apps/shelve/app/components/CommandPalette.vue @@ -267,6 +267,9 @@ function playAction(item: CommandItem, index: number) { + + {{ item.suffix }} + { + try { + const projects = useProjects(team.slug) + if (!projects.value || projects.value.length === 0) { + projects.value = await $fetch(`/api/teams/${team.slug}/projects`) + } + } catch (error) { + console.error(`Failed to fetch projects for ${team.slug}:`, error) + } + })) + } + + // Eagerly fetch projects when teams are available + watch(teams, (newTeams) => { + if (newTeams && newTeams.length > 0) { + fetchAllProjects() + } + }, { immediate: true }) + // Submenu state const subMenuState = reactive({ active: false, @@ -252,6 +276,40 @@ export function useAppCommands() { })) }) + // Project commands - all projects from all teams + const projectCommands = computed(() => { + if (!teams.value || teams.value.length === 0) return [] + + const currentTeamSlug = getTeamSlug() + const allProjects: CommandItem[] = [] + + // useProjects returns useState keyed by slug - safe to call in computed + for (const team of teams.value) { + const projects = useProjects(team.slug) + if (!projects.value || projects.value.length === 0) continue + + const isCurrentTeam = team.slug === currentTeamSlug + for (const project of projects.value) { + allProjects.push({ + id: `project-${project.id}`, + label: project.name, + icon: project.logo || 'lucide:folder', + isAvatar: Boolean(project.logo), + suffix: isCurrentTeam ? undefined : team.name, + description: isCurrentTeam ? undefined : `in ${team.name}`, + action: () => navigateTo(`/${team.slug}/projects/${project.id}`), + keywords: ['project', 'switch', project.name, team.name], + active: route.params.projectId === String(project.id), + }) + } + } + + // Hide if only 1 project total + if (allProjects.length <= 1) return [] + + return allProjects + }) + // Help & Support commands const helpCommands = computed(() => [ { @@ -346,6 +404,15 @@ export function useAppCommands() { }) } + // Only show projects if available + if (projectCommands.value.length > 0) { + groups.push({ + id: 'projects', + label: 'Projects', + items: projectCommands.value + }) + } + // Only show navigation if we have teams if (teams.value && teams.value.length > 0) { groups.push({ diff --git a/packages/types/src/Command.ts b/packages/types/src/Command.ts index afc3ce53..d467151f 100644 --- a/packages/types/src/Command.ts +++ b/packages/types/src/Command.ts @@ -4,6 +4,7 @@ export interface CommandItem { icon: string isAvatar?: boolean description?: string + suffix?: string action?: () => void | Promise keywords?: string[] active?: boolean From d8ad3337002d84111cd77ccb2314bc8fc19048a8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:32 +0000 Subject: [PATCH 2/2] chore: apply automated lint fixes --- apps/shelve/app/composables/useAppCommands.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/shelve/app/composables/useAppCommands.ts b/apps/shelve/app/composables/useAppCommands.ts index 9f538960..c3e0c534 100644 --- a/apps/shelve/app/composables/useAppCommands.ts +++ b/apps/shelve/app/composables/useAppCommands.ts @@ -1,5 +1,4 @@ -import type { CommandGroup, CommandItem, SubMenuState } from '@types' -import type { Project } from '@types' +import type { CommandGroup, CommandItem, SubMenuState, Project } from '@types' export function useAppCommands() { const teams = useTeams()