diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 779d7146323..a9b60b29316 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -143,12 +143,14 @@ export default function Sidebar({ ref }: { ref?: Ref }) { return } + if (activeTab?.url === path) return + if (activeTab?.isPinned) { openTab(path, { forceNew: true, title: getDefaultRouteTitle(path) }) return } - if (activeTab && activeTab.id !== 'home') { + if (activeTab) { // Reusing the active tab — clear any per-entity icon (e.g. a mini-app // logo carried over from /app/mini-app/) so the new top-level // route falls back to its default Lucide icon. diff --git a/src/renderer/src/components/layout/AppShellTabBar.tsx b/src/renderer/src/components/layout/AppShellTabBar.tsx index 58d5f85b64b..574d7a62f3a 100644 --- a/src/renderer/src/components/layout/AppShellTabBar.tsx +++ b/src/renderer/src/components/layout/AppShellTabBar.tsx @@ -11,7 +11,7 @@ import { getMiniAppsLogo } from '@renderer/config/miniApps' import useMacTransparentWindow from '@renderer/hooks/useMacTransparentWindow' import { cn, uuid } from '@renderer/utils' import { getDefaultRouteTitle } from '@renderer/utils/routeTitle' -import { ChevronsLeft, Home, Pin, PinOff, Plus, X } from 'lucide-react' +import { ChevronsLeft, Pin, PinOff, Plus, X } from 'lucide-react' import type { FC } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -42,7 +42,8 @@ const TabIcon: FC<{ tab: Tab; size: number; className?: string }> = ({ tab, size return } -const HOME_TAB_ID = 'home' +const DEFAULT_TAB_ID = 'chat' +const LAUNCHPAD_URL = '/app/launchpad' // ─── Props ──────────────────────────────────────────────────────────────────── @@ -77,30 +78,6 @@ interface TabToneProps { const Separator = () =>
-const HomeTabButton = ({ - isActive, - onClick, - tooltip, - tone -}: { - isActive: boolean - onClick: () => void - tooltip: string - tone: TabToneProps -}) => ( - - - -) - type PinnedTabButtonProps = { tab: Tab isActive: boolean @@ -174,7 +151,7 @@ const NormalTabButton = ({ ref, ...rest }: NormalTabButtonProps) => { - const isCloseable = tab.id !== HOME_TAB_ID + const isCloseable = tab.id !== DEFAULT_TAB_ID const btnRef = useRef(null) const [isNarrow, setIsNarrow] = useState(false) @@ -347,19 +324,22 @@ export const AppShellTabBar = ({ [isMacTransparentWindow] ) - const { homeTab, pinnedTabs, normalTabs } = useMemo(() => { + const { defaultTab, pinnedTabs, normalTabs } = useMemo(() => { const pinned: Tab[] = [] const normal: Tab[] = [] - const home = tabs.find((tab) => tab.id === HOME_TAB_ID) + let defaultRouteTab: Tab | undefined for (const tab of tabs) { - if (tab.id === HOME_TAB_ID) continue + if (tab.id === DEFAULT_TAB_ID) { + defaultRouteTab = tab + continue + } if (tab.isPinned) { pinned.push(tab) } else { normal.push(tab) } } - return { homeTab: home, pinnedTabs: pinned, normalTabs: normal } + return { defaultTab: defaultRouteTab, pinnedTabs: pinned, normalTabs: normal } }, [tabs]) // ─── Context menu actions ─────────────────────────────────────────────────── @@ -397,25 +377,12 @@ export const AppShellTabBar = ({ // ─── Action handlers ──────────────────────────────────────────────────────── - const handleHomeClick = () => { - if (homeTab) { - setActiveTab(homeTab.id) - return - } - addTab({ - id: HOME_TAB_ID, - type: 'route', - url: '/home', - title: getDefaultRouteTitle('/home') - }) - } - const handleAddTab = () => { addTab({ id: uuid(), type: 'route', - url: '/', - title: getDefaultRouteTitle('/') + url: LAUNCHPAD_URL, + title: getDefaultRouteTitle(LAUNCHPAD_URL) }) } @@ -431,19 +398,33 @@ export const AppShellTabBar = ({ rightPaddingClass, isMac ? 'pl-[env(titlebar-area-x)]' : 'pl-3' )}> - {/* Home tab */} - {!isDetached && ( - - )} - {!isDetached && (pinnedTabs.length > 0 || normalTabs.length > 0) && } - {/* Tabs scrollable area — empty space stays draggable; only interactive elements override */}
+ {defaultTab && ( + setActiveTab(defaultTab.id)} + onClose={() => undefined} + showClose={!isDetached} + tone={tabTone} + drag={{ + isDragging: false, + isGhost: false, + noTransition, + translateX: 0, + onPointerDown: () => undefined + }} + tabRef={(el) => { + if (el) { + tabRefs.current.set(defaultTab.id, el) + } else { + tabRefs.current.delete(defaultTab.id) + } + }} + /> + )} + {/* Pinned tabs */} {pinnedTabs.length > 0 && (
diff --git a/src/renderer/src/components/layout/tabIcons.ts b/src/renderer/src/components/layout/tabIcons.ts index a6bff62391a..70c7f0fbc07 100644 --- a/src/renderer/src/components/layout/tabIcons.ts +++ b/src/renderer/src/components/layout/tabIcons.ts @@ -5,8 +5,8 @@ import { Files, FileText, Globe, - Home, Languages, + LayoutGrid, MessageCircle, Palette, Settings, @@ -20,9 +20,8 @@ export type IconComponent = React.FC<{ size?: number; strokeWidth?: number; clas // ─── Route → Icon mapping ───────────────────────────────────────────────────── export const ROUTE_ICONS: Record = { - '/': Home, - '/home': Home, '/app/chat': MessageCircle, + '/app/launchpad': LayoutGrid, '/app/agents': Bot, '/app/assistant': Sparkles, '/app/paintings': Palette, diff --git a/src/renderer/src/context/TabsContext.tsx b/src/renderer/src/context/TabsContext.tsx index f85aa47356d..e57fa75621f 100644 --- a/src/renderer/src/context/TabsContext.tsx +++ b/src/renderer/src/context/TabsContext.tsx @@ -11,9 +11,9 @@ import { createContext, use, useCallback, useEffect, useMemo, useRef, useState } const logger = loggerService.withContext('TabsContext') const DEFAULT_TAB: Tab = { - id: 'home', + id: 'chat', type: 'route', - url: '/home', + url: '/app/chat', title: '', lastAccessTime: Date.now(), isDormant: false @@ -104,8 +104,8 @@ export function TabsProvider({ children }: { children: ReactNode }) { [setPinnedTabsRaw] ) - // Normal tabs - in-memory storage (cleared on restart), excludes home tab - const [normalTabs, setNormalTabs] = useState([]) + // Normal tabs - in-memory storage (cleared on restart), includes the non-closeable default tab + const [normalTabs, setNormalTabs] = useState(() => [DEFAULT_TAB]) // Active tab ID - in-memory storage const [activeTabId, setActiveTabIdState] = useState(DEFAULT_TAB.id) @@ -133,10 +133,9 @@ export function TabsProvider({ children }: { children: ReactNode }) { }) }, []) - // Merge tabs: home + pinned + normal (route titles follow current i18n language) + // Merge tabs: pinned + normal (route titles follow current i18n language) const tabs = useMemo(() => { - const home = withLocalizedRouteTitle({ ...DEFAULT_TAB }) - return [home, ...(pinnedTabs || []).map(withLocalizedRouteTitle), ...normalTabs.map(withLocalizedRouteTitle)] + return [...(pinnedTabs || []).map(withLocalizedRouteTitle), ...normalTabs.map(withLocalizedRouteTitle)] }, [pinnedTabs, normalTabs]) /** diff --git a/src/renderer/src/routeTree.gen.ts b/src/renderer/src/routeTree.gen.ts index 983cc819672..1a9e92a6f69 100644 --- a/src/renderer/src/routeTree.gen.ts +++ b/src/renderer/src/routeTree.gen.ts @@ -10,9 +10,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as SettingsRouteImport } from './routes/settings' -import { Route as HomeRouteImport } from './routes/home' import { Route as AppRouteImport } from './routes/app' -import { Route as IndexRouteImport } from './routes/index' import { Route as SettingsIndexRouteImport } from './routes/settings/index' import { Route as SettingsWebsearchRouteImport } from './routes/settings/websearch' import { Route as SettingsSkillsRouteImport } from './routes/settings/skills' @@ -37,6 +35,7 @@ import { Route as AppTranslateRouteImport } from './routes/app/translate' import { Route as AppOpenclawRouteImport } from './routes/app/openclaw' import { Route as AppNotesRouteImport } from './routes/app/notes' import { Route as AppLibraryRouteImport } from './routes/app/library' +import { Route as AppLaunchpadRouteImport } from './routes/app/launchpad' import { Route as AppKnowledgeRouteImport } from './routes/app/knowledge' import { Route as AppFilesRouteImport } from './routes/app/files' import { Route as AppCodeRouteImport } from './routes/app/code' @@ -61,21 +60,11 @@ const SettingsRoute = SettingsRouteImport.update({ path: '/settings', getParentRoute: () => rootRouteImport, } as any) -const HomeRoute = HomeRouteImport.update({ - id: '/home', - path: '/home', - getParentRoute: () => rootRouteImport, -} as any) const AppRoute = AppRouteImport.update({ id: '/app', path: '/app', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport, -} as any) const SettingsIndexRoute = SettingsIndexRouteImport.update({ id: '/', path: '/', @@ -197,6 +186,11 @@ const AppLibraryRoute = AppLibraryRouteImport.update({ path: '/library', getParentRoute: () => AppRoute, } as any) +const AppLaunchpadRoute = AppLaunchpadRouteImport.update({ + id: '/launchpad', + path: '/launchpad', + getParentRoute: () => AppRoute, +} as any) const AppKnowledgeRoute = AppKnowledgeRouteImport.update({ id: '/knowledge', path: '/knowledge', @@ -290,9 +284,7 @@ const SettingsMcpSettingsServerIdRoute = } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute '/app': typeof AppRouteWithChildren - '/home': typeof HomeRoute '/settings': typeof SettingsRouteWithChildren '/app/agents': typeof AppAgentsRoute '/app/assistant': typeof AppAssistantRoute @@ -300,6 +292,7 @@ export interface FileRoutesByFullPath { '/app/code': typeof AppCodeRoute '/app/files': typeof AppFilesRoute '/app/knowledge': typeof AppKnowledgeRoute + '/app/launchpad': typeof AppLaunchpadRoute '/app/library': typeof AppLibraryRoute '/app/notes': typeof AppNotesRoute '/app/openclaw': typeof AppOpenclawRoute @@ -338,15 +331,14 @@ export interface FileRoutesByFullPath { '/settings/mcp/settings/$serverId': typeof SettingsMcpSettingsServerIdRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute '/app': typeof AppRouteWithChildren - '/home': typeof HomeRoute '/app/agents': typeof AppAgentsRoute '/app/assistant': typeof AppAssistantRoute '/app/chat': typeof AppChatRoute '/app/code': typeof AppCodeRoute '/app/files': typeof AppFilesRoute '/app/knowledge': typeof AppKnowledgeRoute + '/app/launchpad': typeof AppLaunchpadRoute '/app/library': typeof AppLibraryRoute '/app/notes': typeof AppNotesRoute '/app/openclaw': typeof AppOpenclawRoute @@ -385,9 +377,7 @@ export interface FileRoutesByTo { } export interface FileRoutesById { __root__: typeof rootRouteImport - '/': typeof IndexRoute '/app': typeof AppRouteWithChildren - '/home': typeof HomeRoute '/settings': typeof SettingsRouteWithChildren '/app/agents': typeof AppAgentsRoute '/app/assistant': typeof AppAssistantRoute @@ -395,6 +385,7 @@ export interface FileRoutesById { '/app/code': typeof AppCodeRoute '/app/files': typeof AppFilesRoute '/app/knowledge': typeof AppKnowledgeRoute + '/app/launchpad': typeof AppLaunchpadRoute '/app/library': typeof AppLibraryRoute '/app/notes': typeof AppNotesRoute '/app/openclaw': typeof AppOpenclawRoute @@ -435,9 +426,7 @@ export interface FileRoutesById { export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: - | '/' | '/app' - | '/home' | '/settings' | '/app/agents' | '/app/assistant' @@ -445,6 +434,7 @@ export interface FileRouteTypes { | '/app/code' | '/app/files' | '/app/knowledge' + | '/app/launchpad' | '/app/library' | '/app/notes' | '/app/openclaw' @@ -483,15 +473,14 @@ export interface FileRouteTypes { | '/settings/mcp/settings/$serverId' fileRoutesByTo: FileRoutesByTo to: - | '/' | '/app' - | '/home' | '/app/agents' | '/app/assistant' | '/app/chat' | '/app/code' | '/app/files' | '/app/knowledge' + | '/app/launchpad' | '/app/library' | '/app/notes' | '/app/openclaw' @@ -529,9 +518,7 @@ export interface FileRouteTypes { | '/settings/mcp/settings/$serverId' id: | '__root__' - | '/' | '/app' - | '/home' | '/settings' | '/app/agents' | '/app/assistant' @@ -539,6 +526,7 @@ export interface FileRouteTypes { | '/app/code' | '/app/files' | '/app/knowledge' + | '/app/launchpad' | '/app/library' | '/app/notes' | '/app/openclaw' @@ -578,9 +566,7 @@ export interface FileRouteTypes { fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute AppRoute: typeof AppRouteWithChildren - HomeRoute: typeof HomeRoute SettingsRoute: typeof SettingsRouteWithChildren } @@ -593,13 +579,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SettingsRouteImport parentRoute: typeof rootRouteImport } - '/home': { - id: '/home' - path: '/home' - fullPath: '/home' - preLoaderRoute: typeof HomeRouteImport - parentRoute: typeof rootRouteImport - } '/app': { id: '/app' path: '/app' @@ -607,13 +586,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } '/settings/': { id: '/settings/' path: '/' @@ -782,6 +754,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppLibraryRouteImport parentRoute: typeof AppRoute } + '/app/launchpad': { + id: '/app/launchpad' + path: '/launchpad' + fullPath: '/app/launchpad' + preLoaderRoute: typeof AppLaunchpadRouteImport + parentRoute: typeof AppRoute + } '/app/knowledge': { id: '/app/knowledge' path: '/knowledge' @@ -918,6 +897,7 @@ interface AppRouteChildren { AppCodeRoute: typeof AppCodeRoute AppFilesRoute: typeof AppFilesRoute AppKnowledgeRoute: typeof AppKnowledgeRoute + AppLaunchpadRoute: typeof AppLaunchpadRoute AppLibraryRoute: typeof AppLibraryRoute AppNotesRoute: typeof AppNotesRoute AppOpenclawRoute: typeof AppOpenclawRoute @@ -935,6 +915,7 @@ const AppRouteChildren: AppRouteChildren = { AppCodeRoute: AppCodeRoute, AppFilesRoute: AppFilesRoute, AppKnowledgeRoute: AppKnowledgeRoute, + AppLaunchpadRoute: AppLaunchpadRoute, AppLibraryRoute: AppLibraryRoute, AppNotesRoute: AppNotesRoute, AppOpenclawRoute: AppOpenclawRoute, @@ -1024,9 +1005,7 @@ const SettingsRouteWithChildren = SettingsRoute._addFileChildren( ) const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, AppRoute: AppRouteWithChildren, - HomeRoute: HomeRoute, SettingsRoute: SettingsRouteWithChildren, } export const routeTree = rootRouteImport diff --git a/src/renderer/src/routes/home.tsx b/src/renderer/src/routes/app/launchpad.tsx similarity index 73% rename from src/renderer/src/routes/home.tsx rename to src/renderer/src/routes/app/launchpad.tsx index 5cf75bbeeaf..eb0ea1ab5f3 100644 --- a/src/renderer/src/routes/home.tsx +++ b/src/renderer/src/routes/app/launchpad.tsx @@ -1,6 +1,6 @@ import LaunchpadPage from '@renderer/pages/launchpad/LaunchpadPage' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/home')({ +export const Route = createFileRoute('/app/launchpad')({ component: LaunchpadPage }) diff --git a/src/renderer/src/routes/index.tsx b/src/renderer/src/routes/index.tsx deleted file mode 100644 index 4f341f0857b..00000000000 --- a/src/renderer/src/routes/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createFileRoute, redirect } from '@tanstack/react-router' - -export const Route = createFileRoute('/')({ - beforeLoad: () => { - throw redirect({ to: '/home' }) - } -}) diff --git a/src/renderer/src/services/TabLRUManager.ts b/src/renderer/src/services/TabLRUManager.ts index c536bc5d769..e6bbd8f9a00 100644 --- a/src/renderer/src/services/TabLRUManager.ts +++ b/src/renderer/src/services/TabLRUManager.ts @@ -31,7 +31,7 @@ export type TabLimits = typeof TAB_LIMITS * 功能: * - 当活跃标签数超过软上限时,选择 LRU 候选进行休眠 * - 硬保险丝作为极端兜底,防止内存失控 - * - 支持豁免机制:当前标签、首页、置顶标签不参与休眠 + * - 支持豁免机制:当前标签、默认聊天标签、置顶标签不参与休眠 */ export class TabLRUManager { private softCap: number @@ -47,7 +47,7 @@ export class TabLRUManager { * * 策略: * - 超过 softCap:休眠到 softCap - * - 超过 hardCap:强制休眠到 softCap(忽略部分豁免,仅保留当前+首页) + * - 超过 hardCap:强制休眠到 softCap(忽略部分豁免,仅保留当前+默认聊天标签) * * @param tabs 所有标签 * @param activeTabId 当前活动标签 ID @@ -65,7 +65,7 @@ export class TabLRUManager { const isHardCapTriggered = activeCount > this.hardCap // 获取候选列表 - // 硬保险丝触发时,使用更宽松的豁免规则(仅保留当前+首页) + // 硬保险丝触发时,使用更宽松的豁免规则(仅保留当前+默认聊天标签) const candidates = isHardCapTriggered ? this.getHardCapCandidates(activeTabs, activeTabId) : this.getLRUCandidates(activeTabs, activeTabId) @@ -123,7 +123,7 @@ export class TabLRUManager { } /** - * 硬保险丝候选列表(仅豁免当前标签和首页) + * 硬保险丝候选列表(仅豁免当前标签和默认聊天标签) */ private getHardCapCandidates(tabs: Tab[], activeTabId: string): Tab[] { return tabs @@ -132,12 +132,12 @@ export class TabLRUManager { } /** - * 硬保险丝豁免判断(更严格,仅保留当前+首页) + * 硬保险丝豁免判断(更严格,仅保留当前+默认聊天标签) */ private isHardExempt(tab: Tab, activeTabId: string): boolean { return ( tab.id === activeTabId || // 当前活动标签 - tab.id === 'home' || // 首页 + tab.id === 'chat' || // 默认聊天标签 tab.isDormant === true // 已休眠的不再参与 ) // 注意:isPinned 在硬保险丝触发时不再豁免 @@ -157,14 +157,14 @@ export class TabLRUManager { * * 豁免条件: * - 当前活动标签 - * - 首页标签 (id === 'home') + * - 默认聊天标签 (id === 'chat') * - 置顶标签 (isPinned) * - 已休眠的标签(不重复处理) */ private isExempt(tab: Tab, activeTabId: string): boolean { return ( tab.id === activeTabId || // 当前活动标签 - tab.id === 'home' || // 首页 + tab.id === 'chat' || // 默认聊天标签 tab.isPinned === true || // 置顶标签 tab.isDormant === true // 已休眠的不再参与 ) diff --git a/src/renderer/src/services/__tests__/TabLRUManager.test.ts b/src/renderer/src/services/__tests__/TabLRUManager.test.ts index 076f680a998..e7d8faec81d 100644 --- a/src/renderer/src/services/__tests__/TabLRUManager.test.ts +++ b/src/renderer/src/services/__tests__/TabLRUManager.test.ts @@ -85,10 +85,10 @@ describe('TabLRUManager', () => { expect(result).not.toContain('tab-0') }) - it('should not hibernate the home tab', () => { + it('should not hibernate the default chat tab', () => { const now = Date.now() const tabs = [ - createTab('home', { lastAccessTime: now - 10000 }), // Oldest + createTab('chat', { lastAccessTime: now - 10000 }), // Oldest ...Array.from({ length: TAB_LIMITS.softCap + 1 }, (_, i) => createTab(`tab-${i}`, { lastAccessTime: now + i * 1000 }) ) @@ -96,7 +96,7 @@ describe('TabLRUManager', () => { const result = manager.checkAndGetDormantCandidates(tabs, `tab-${TAB_LIMITS.softCap}`) - expect(result).not.toContain('home') + expect(result).not.toContain('chat') }) it('should not hibernate pinned tabs', () => { @@ -141,14 +141,14 @@ describe('TabLRUManager', () => { const result = manager.checkAndGetDormantCandidates(tabs, `tab-${TAB_LIMITS.hardCap + 1}`) - // Hard cap triggered: pinned tabs are no longer exempt (except home and active) + // Hard cap triggered: pinned tabs are no longer exempt (except the default chat tab and active) expect(result).toContain('pinned-old') }) - it('should still protect home and active tabs in hard cap mode', () => { + it('should still protect the default chat and active tabs in hard cap mode', () => { const now = Date.now() const tabs = [ - createTab('home', { lastAccessTime: now - 30000 }), + createTab('chat', { lastAccessTime: now - 30000 }), ...Array.from({ length: TAB_LIMITS.hardCap + 2 }, (_, i) => createTab(`tab-${i}`, { lastAccessTime: now + i * 1000 }) ) @@ -157,7 +157,7 @@ describe('TabLRUManager', () => { const activeTabId = `tab-${TAB_LIMITS.hardCap + 1}` const result = manager.checkAndGetDormantCandidates(tabs, activeTabId) - expect(result).not.toContain('home') + expect(result).not.toContain('chat') expect(result).not.toContain(activeTabId) }) }) diff --git a/src/renderer/src/utils/__tests__/routeTitle.test.ts b/src/renderer/src/utils/__tests__/routeTitle.test.ts index 4be5ae630ff..52a3a17114c 100644 --- a/src/renderer/src/utils/__tests__/routeTitle.test.ts +++ b/src/renderer/src/utils/__tests__/routeTitle.test.ts @@ -5,8 +5,8 @@ vi.mock('@renderer/i18n', () => ({ default: { t: vi.fn((key: string) => { const translations: Record = { - 'title.home': '首页', 'common.chat': '聊天', + 'title.launchpad': '启动台', 'common.agent_one': '智能体', 'title.store': '助手库', 'title.paintings': '绘画', @@ -34,9 +34,8 @@ describe('routeTitle', () => { describe('getDefaultRouteTitle', () => { describe('exact route matches', () => { it.each([ - ['/', '首页'], - ['/home', '首页'], ['/app/chat', '聊天'], + ['/app/launchpad', '启动台'], ['/app/agents', '智能体'], ['/app/assistant', '助手库'], ['/app/paintings', '绘画'], @@ -56,6 +55,7 @@ describe('routeTitle', () => { describe('nested route matches', () => { it('should match base path for nested routes', () => { expect(getDefaultRouteTitle('/app/chat/topic-123')).toBe('聊天') + expect(getDefaultRouteTitle('/app/launchpad/pinned')).toBe('启动台') expect(getDefaultRouteTitle('/app/agents/session-123')).toBe('智能体') expect(getDefaultRouteTitle('/settings/provider')).toBe('设置') expect(getDefaultRouteTitle('/settings/mcp/servers')).toBe('设置') @@ -98,7 +98,7 @@ describe('routeTitle', () => { it('should handle double slashes (protocol-relative URL)', () => { // '//chat' is a protocol-relative URL, so 'chat' becomes the hostname // This is expected behavior per URL standard - expect(getDefaultRouteTitle('//chat')).toBe('首页') + expect(getDefaultRouteTitle('//chat')).toBe('/') }) it('should handle relative-like paths', () => { @@ -112,8 +112,8 @@ describe('routeTitle', () => { describe('getRouteTitleKey', () => { describe('exact matches', () => { it.each([ - ['/', 'title.home'], ['/app/chat', 'common.chat'], + ['/app/launchpad', 'title.launchpad'], ['/app/agents', 'common.agent_one'], ['/app/assistant', 'title.store'], ['/app/openclaw', 'title.openclaw'], @@ -126,6 +126,7 @@ describe('routeTitle', () => { describe('base path matches', () => { it('should return base path key for nested routes', () => { expect(getRouteTitleKey('/app/chat/topic-123')).toBe('common.chat') + expect(getRouteTitleKey('/app/launchpad/pinned')).toBe('title.launchpad') expect(getRouteTitleKey('/app/agents/session-123')).toBe('common.agent_one') expect(getRouteTitleKey('/settings/provider')).toBe('title.settings') }) diff --git a/src/renderer/src/utils/routeTitle.ts b/src/renderer/src/utils/routeTitle.ts index 729bd142ef0..38de9fa256f 100644 --- a/src/renderer/src/utils/routeTitle.ts +++ b/src/renderer/src/utils/routeTitle.ts @@ -7,9 +7,8 @@ const BASE_URL = 'https://www.cherry-ai.com/' * Route to i18n key mapping for default tab titles */ const routeTitleKeys: Record = { - '/': 'title.home', - '/home': 'title.home', '/app/chat': 'common.chat', + '/app/launchpad': 'title.launchpad', '/app/agents': 'common.agent_one', '/app/assistant': 'title.store', '/app/paintings': 'title.paintings', diff --git a/src/renderer/src/utils/window.ts b/src/renderer/src/utils/window.ts index ed50c388209..81b99b20970 100644 --- a/src/renderer/src/utils/window.ts +++ b/src/renderer/src/utils/window.ts @@ -3,5 +3,5 @@ export const isFocused = () => { } export const isOnHomePage = () => { - return window.location.hash === '#/' || window.location.hash === '#' || window.location.hash === '' + return window.location.hash === '#/app/chat' || window.location.hash === '#' || window.location.hash === '' }