From 3d31be3a3f5cd1f701bc6d1aba30140eaf4b4fdf Mon Sep 17 00:00:00 2001 From: Lexora <345637437+lexora-agent@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:27:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(buddy):=20=E8=AE=B0=E4=BD=8F=E4=B8=8A?= =?UTF-8?q?=E6=AC=A1=E8=AE=BF=E9=97=AE=E7=9A=84=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/__tests__/settings.e2e.mjs | 22 ++++++++++ .../app/router/__tests__/desktopPages.spec.ts | 42 +++++++++++++++++++ apps/buddy/src/app/router/useDesktopPages.ts | 10 ++++- apps/buddy/src/modules/settings/routes.ts | 1 + .../src/shared/navigation/desktopPages.ts | 1 + 5 files changed, 74 insertions(+), 2 deletions(-) diff --git a/.playwright/scripts/__tests__/settings.e2e.mjs b/.playwright/scripts/__tests__/settings.e2e.mjs index b1e52fd54..aa8b880f4 100644 --- a/.playwright/scripts/__tests__/settings.e2e.mjs +++ b/.playwright/scripts/__tests__/settings.e2e.mjs @@ -78,6 +78,28 @@ test('prompt loading can retry and late responses do not replace another setting expect(diagnostics.console.filter(item => item.type === 'pageerror')).toEqual([]) }) +test('settings navigation returns to the last page and still allows explicit page selection', async ({ buddy }) => { + const instance = await buddy.createInstance('settings-navigation') + const { page, diagnostics } = await instance.launch() + const navigation = page.locator('.desktop-app-sidebar') + const title = page.locator('.desktop-settings-page__title') + await navigation.getByRole('button', { name: '设置', exact: true }).click() + await expect(title).toHaveText('常规') + + for (const [name, category] of [['快捷键', 'shortcuts'], ['外观', 'appearance'], ['常规', 'general']]) { + await page.locator('.desktop-settings-sidebar').getByRole('link', { name, exact: true }).click() + await expect(title).toHaveText(name) + await navigation.getByRole('button', { name: '任务', exact: true }).click() + await expect(page).toHaveURL(/#\/tasks$/) + await navigation.getByRole('button', { name: '设置', exact: true }).click() + await expect(page).toHaveURL(new RegExp(`#/settings/${category}$`)) + await expect(title).toHaveText(name) + await navigation.getByRole('button', { name: '设置', exact: true }).click() + await expect(title).toHaveText(name) + } + expect(diagnostics.console.filter(item => item.type === 'pageerror')).toEqual([]) +}) + test('builtin settings preserve legacy navigation, failed-save rollback and restart persistence', async ({ buddy }) => { const instance = await buddy.createInstance('settings') let desktop = await instance.launch() diff --git a/apps/buddy/src/app/router/__tests__/desktopPages.spec.ts b/apps/buddy/src/app/router/__tests__/desktopPages.spec.ts index 24f7f28cb..1d5055c94 100644 --- a/apps/buddy/src/app/router/__tests__/desktopPages.spec.ts +++ b/apps/buddy/src/app/router/__tests__/desktopPages.spec.ts @@ -6,6 +6,7 @@ import { builtinSettingsCatalog } from '@buddy-shared/settings/settingsCatalog' import { expect, it } from 'vitest' import { shallowRef } from 'vue' import { createMemoryHistory, createRouter } from 'vue-router' +import { settingsPage } from '@/modules/settings/routes' import { desktopPageRoutes } from '@/shared/navigation/desktopPages' import { desktopRouteLocations } from '@/shared/navigation/desktopRoutes' import { createDesktopRouter } from '../index' @@ -33,6 +34,47 @@ it('derives navigation and context from registered pages, including future modul expect(() => desktopPageRoutes([settings, settings])).toThrow('already registered') }) +it('restores the last successful settings location without overriding explicit destinations', async () => { + const settings: DesktopPageContribution = { + ...settingsPage, + routes: [{ + path: '/settings', + component: { render: () => null }, + children: [ + { path: 'general', name: 'desktop.settings.general', component: { render: () => null } }, + { path: ':category', component: { render: () => null } }, + { path: 'models/:providerId', component: { render: () => null } }, + { path: 'plugins/:moduleId', component: { render: () => null } }, + ], + }], + } + const router = createRouter({ history: createMemoryHistory(), routes: desktopPageRoutes([page('lexora.tasks', '/tasks'), settings]) }) + const pages = useDesktopPages(router, shallowRef([]), shallowRef('en-US' as const)) + const settingsLocation = () => pages.navigation.value.find(entry => entry.id === settings.id)!.location + expect(router.resolve(settingsLocation()).fullPath).toBe('/settings/general') + await router.push(settingsLocation()) + expect(router.currentRoute.value.fullPath).toBe('/settings/general') + + for (const location of ['/settings/appearance', '/settings/models/test.provider?group=details#credentials', '/settings/plugins/test.preferences?group=display']) { + await router.push(location) + await router.push('/tasks') + await router.push(settingsLocation()) + expect(router.currentRoute.value.fullPath).toBe(location) + } + + const removeGuard = router.beforeEach(to => to.path !== '/settings/notifications') + await router.push('/settings/notifications') + await router.push('/tasks') + expect(settingsLocation()).toBe('/settings/plugins/test.preferences?group=display') + removeGuard() + + await router.push(desktopRouteLocations.settings()) + expect(router.currentRoute.value.fullPath).toBe('/settings/general') + await router.push('/tasks') + await router.push(settingsLocation()) + expect(router.currentRoute.value.fullPath).toBe('/settings/general') +}) + it('allows cold-start access only for trusted runtime-independent settings routes', () => { const router = createDesktopRouter(createMemoryHistory()) for (const [category, capability] of Object.entries(builtinSettingsCatalog)) { diff --git a/apps/buddy/src/app/router/useDesktopPages.ts b/apps/buddy/src/app/router/useDesktopPages.ts index a745e3f32..e8deacaca 100644 --- a/apps/buddy/src/app/router/useDesktopPages.ts +++ b/apps/buddy/src/app/router/useDesktopPages.ts @@ -5,7 +5,7 @@ import type { Router } from 'vue-router' import type { BuddyLocale } from '@/i18n/buddyI18n' import type { DesktopNavigationEntry, DesktopPageDefinition } from '@/shared/navigation/desktopPages' import { matchesWorkbenchContext } from '@buddy-shared/workbench/workbenchContext' -import { computed } from 'vue' +import { computed, shallowReactive, watch } from 'vue' import { extensionPageDefinitions } from '@/modules/extensions/routes' import { resolveDesktopPageId } from '@/shared/navigation/desktopPages' @@ -22,6 +22,12 @@ export function useDesktopPages(router: Router, installed: Readonly a.order - b.order || a.id.localeCompare(b.id)) }) const current = computed(() => resolveDesktopPageId(router.currentRoute.value)) + const lastLocations = shallowReactive(new Map()) + watch(router.currentRoute, (route) => { + const page = definitions.value.find(page => page.id === resolveDesktopPageId(route)) + if (page?.rememberLastLocation) + lastLocations.set(page.id, route.fullPath) + }, { immediate: true, flush: 'sync' }) const context = computed((previous) => { const next = { values: { ...definitions.value.find(page => page.id === current.value)?.context?.(router.currentRoute.value), 'page.id': current.value }, @@ -29,6 +35,6 @@ export function useDesktopPages(router: Router, installed: Readonly(() => definitions.value.filter(page => matchesWorkbenchContext(page.when, context.value.values)).map(page => ({ id: page.id, title: page.title(language.value), location: page.location, icon: page.icon, extensionId: page.extensionId, active: page.id === current.value }))) + const navigation = computed(() => definitions.value.filter(page => matchesWorkbenchContext(page.when, context.value.values)).map(page => ({ id: page.id, title: page.title(language.value), location: (page.rememberLastLocation && lastLocations.get(page.id)) || page.location, icon: page.icon, extensionId: page.extensionId, active: page.id === current.value }))) return { current, context, navigation } } diff --git a/apps/buddy/src/modules/settings/routes.ts b/apps/buddy/src/modules/settings/routes.ts index 58eb5f96f..80e0c3635 100644 --- a/apps/buddy/src/modules/settings/routes.ts +++ b/apps/buddy/src/modules/settings/routes.ts @@ -43,6 +43,7 @@ export const settingsPage: DesktopPageContribution = { icon: { kind: 'named', name: 'navigationSettings' }, order: 50, location: desktopRouteLocations.settings(), + rememberLastLocation: true, routes: settingsRoutes, context: route => ({ 'page.section': route.meta.settingsCategory ?? '' }), } diff --git a/apps/buddy/src/shared/navigation/desktopPages.ts b/apps/buddy/src/shared/navigation/desktopPages.ts index e872a8b25..38fb9e12f 100644 --- a/apps/buddy/src/shared/navigation/desktopPages.ts +++ b/apps/buddy/src/shared/navigation/desktopPages.ts @@ -8,6 +8,7 @@ export interface DesktopPageDefinition { id: string title: (language: BuddyLocale) => string location: RouteLocationRaw + rememberLastLocation?: boolean order: number icon: { kind: 'named', name: DesktopIconName } | { kind: 'component', component: Component } | { kind: 'plugin', url?: string } extensionId?: string From c5bbfb60613a21f45bfdce39bfac9fea6f9eb664 Mon Sep 17 00:00:00 2001 From: shanyuhai123 <864299347@qq.com> Date: Wed, 21 Oct 2026 17:12:57 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(buddy):=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=AF=BC=E8=88=AA=E4=BD=8D=E7=BD=AE=E8=AE=B0?= =?UTF-8?q?=E5=BF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/buddy/src/app/router/useDesktopPages.ts | 11 +++++------ apps/buddy/src/modules/settings/routes.ts | 1 - apps/buddy/src/shared/navigation/desktopPages.ts | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/buddy/src/app/router/useDesktopPages.ts b/apps/buddy/src/app/router/useDesktopPages.ts index e8deacaca..2fda1b270 100644 --- a/apps/buddy/src/app/router/useDesktopPages.ts +++ b/apps/buddy/src/app/router/useDesktopPages.ts @@ -5,7 +5,7 @@ import type { Router } from 'vue-router' import type { BuddyLocale } from '@/i18n/buddyI18n' import type { DesktopNavigationEntry, DesktopPageDefinition } from '@/shared/navigation/desktopPages' import { matchesWorkbenchContext } from '@buddy-shared/workbench/workbenchContext' -import { computed, shallowReactive, watch } from 'vue' +import { computed, shallowRef, watch } from 'vue' import { extensionPageDefinitions } from '@/modules/extensions/routes' import { resolveDesktopPageId } from '@/shared/navigation/desktopPages' @@ -22,11 +22,10 @@ export function useDesktopPages(router: Router, installed: Readonly a.order - b.order || a.id.localeCompare(b.id)) }) const current = computed(() => resolveDesktopPageId(router.currentRoute.value)) - const lastLocations = shallowReactive(new Map()) + const lastSettingsLocation = shallowRef() watch(router.currentRoute, (route) => { - const page = definitions.value.find(page => page.id === resolveDesktopPageId(route)) - if (page?.rememberLastLocation) - lastLocations.set(page.id, route.fullPath) + if (resolveDesktopPageId(route) === 'lexora.settings') + lastSettingsLocation.value = route.fullPath }, { immediate: true, flush: 'sync' }) const context = computed((previous) => { const next = { @@ -35,6 +34,6 @@ export function useDesktopPages(router: Router, installed: Readonly(() => definitions.value.filter(page => matchesWorkbenchContext(page.when, context.value.values)).map(page => ({ id: page.id, title: page.title(language.value), location: (page.rememberLastLocation && lastLocations.get(page.id)) || page.location, icon: page.icon, extensionId: page.extensionId, active: page.id === current.value }))) + const navigation = computed(() => definitions.value.filter(page => matchesWorkbenchContext(page.when, context.value.values)).map(page => ({ id: page.id, title: page.title(language.value), location: page.id === 'lexora.settings' ? lastSettingsLocation.value ?? page.location : page.location, icon: page.icon, extensionId: page.extensionId, active: page.id === current.value }))) return { current, context, navigation } } diff --git a/apps/buddy/src/modules/settings/routes.ts b/apps/buddy/src/modules/settings/routes.ts index 80e0c3635..58eb5f96f 100644 --- a/apps/buddy/src/modules/settings/routes.ts +++ b/apps/buddy/src/modules/settings/routes.ts @@ -43,7 +43,6 @@ export const settingsPage: DesktopPageContribution = { icon: { kind: 'named', name: 'navigationSettings' }, order: 50, location: desktopRouteLocations.settings(), - rememberLastLocation: true, routes: settingsRoutes, context: route => ({ 'page.section': route.meta.settingsCategory ?? '' }), } diff --git a/apps/buddy/src/shared/navigation/desktopPages.ts b/apps/buddy/src/shared/navigation/desktopPages.ts index 38fb9e12f..e872a8b25 100644 --- a/apps/buddy/src/shared/navigation/desktopPages.ts +++ b/apps/buddy/src/shared/navigation/desktopPages.ts @@ -8,7 +8,6 @@ export interface DesktopPageDefinition { id: string title: (language: BuddyLocale) => string location: RouteLocationRaw - rememberLastLocation?: boolean order: number icon: { kind: 'named', name: DesktopIconName } | { kind: 'component', component: Component } | { kind: 'plugin', url?: string } extensionId?: string