diff --git a/packages/shared/data/api/apiSchemas.ts b/packages/shared/data/api/apiSchemas.ts index c049cb9261..e405af806e 100644 --- a/packages/shared/data/api/apiSchemas.ts +++ b/packages/shared/data/api/apiSchemas.ts @@ -18,25 +18,6 @@ export type { ConcreteApiPaths } from './apiPaths' * enabling full TypeScript type checking across IPC boundaries. */ export interface ApiSchemas { - /** - * App State storage endpoint - * @example GET /app/state/tabs_state - * @example PUT /app/state/tabs_state { "value": {...} } - */ - '/app/state/:key': { - /** Get app state by key */ - GET: { - params: { key: string } - response: any - } - /** Save app state */ - PUT: { - params: { key: string } - body: any - response: { success: boolean } - } - } - /** * Test items collection endpoint * @example GET /test/items?page=1&limit=10&search=hello diff --git a/packages/shared/data/cache/cacheSchemas.ts b/packages/shared/data/cache/cacheSchemas.ts index ed835eeb36..54a909ba3f 100644 --- a/packages/shared/data/cache/cacheSchemas.ts +++ b/packages/shared/data/cache/cacheSchemas.ts @@ -76,16 +76,36 @@ export const DefaultUseSharedCache: UseSharedCacheSchema = { 'example-key': 'example default value' } +/** + * Tab type for browser-like tabs + */ +export type TabType = 'webview' | 'url' | 'browser' + +export interface Tab { + id: string + type: TabType + url: string + title: string + icon?: string + isKeepAlive?: boolean + metadata?: Record +} + +export interface TabsState { + tabs: Tab[] + activeTabId: string +} + /** * Persist cache schema defining allowed keys and their value types * This ensures type safety and prevents key conflicts */ export type RendererPersistCacheSchema = { - 'example-key': string + 'tabs_state': TabsState } export const DefaultRendererPersistCache: RendererPersistCacheSchema = { - 'example-key': 'example default value' + 'tabs_state': { tabs: [], activeTabId: '' } } /** diff --git a/src/main/data/api/handlers/index.ts b/src/main/data/api/handlers/index.ts index bc911597b5..817a882be8 100644 --- a/src/main/data/api/handlers/index.ts +++ b/src/main/data/api/handlers/index.ts @@ -5,7 +5,6 @@ * TypeScript will error if any endpoint is missing. */ -import { appStateService } from '@data/services/AppStateService' import { TestService } from '@data/services/TestService' import type { ApiImplementation } from '@shared/data/api/apiSchemas' @@ -17,18 +16,6 @@ const testService = TestService.getInstance() * Must implement every path+method combination from ApiSchemas */ export const apiHandlers: ApiImplementation = { - '/app/state/:key': { - GET: async ({ params }) => { - const state = await appStateService.getState(params.key) - return state - }, - - PUT: async ({ params, body }) => { - await appStateService.setState(params.key, body) - return { success: true } - } - }, - '/test/items': { GET: async ({ query }) => { return await testService.getItems({ diff --git a/src/renderer/src/hooks/useTabs.ts b/src/renderer/src/hooks/useTabs.ts index f290942f1d..510e5e6eae 100644 --- a/src/renderer/src/hooks/useTabs.ts +++ b/src/renderer/src/hooks/useTabs.ts @@ -1,78 +1,28 @@ -import { loggerService } from '@logger' import { useCallback, useMemo } from 'react' -import { useMutation, useQuery } from '../data/hooks/useDataApi' +import { usePersistCache } from '../data/hooks/useCache' -const logger = loggerService.withContext('useTabs') - -export type TabType = 'webview' | 'url' | 'browser' - -export interface Tab { - id: string - type: TabType - url: string - title: string - icon?: string - isKeepAlive?: boolean - metadata?: Record -} - -interface TabsState { - tabs: Tab[] - activeTabId: string -} - -const TABS_STORAGE_KEY = 'tabs_state' -const DEFAULT_STATE: TabsState = { tabs: [], activeTabId: '' } +// Re-export types from shared schema +export type { Tab, TabsState, TabType } from '@shared/data/cache/cacheSchemas' +import type { Tab } from '@shared/data/cache/cacheSchemas' export function useTabs() { - // Load state from DB - // We cast the path because we haven't fully updated the concrete path types globally yet - const { - data: tabsState, - mutate: mutateState, - loading: isLoading - } = useQuery(`/app/state/${TABS_STORAGE_KEY}` as any, { - swrOptions: { - revalidateOnFocus: false, - fallbackData: DEFAULT_STATE - } - }) + const [tabsState, setTabsState] = usePersistCache('tabs_state') - // Ensure we always have a valid object structure - const currentState: TabsState = useMemo( - () => (tabsState && typeof tabsState === 'object' ? (tabsState as TabsState) : DEFAULT_STATE), - [tabsState] - ) - const tabs = useMemo(() => (Array.isArray(currentState.tabs) ? currentState.tabs : []), [currentState.tabs]) - const activeTabId = currentState.activeTabId || '' - - // Mutation for saving - const saveMutation = useMutation('PUT', `/app/state/${TABS_STORAGE_KEY}` as any) - - // Unified update helper - const updateState = useCallback( - async (newState: TabsState) => { - // 1. Optimistic update local cache - await mutateState(newState, { revalidate: false }) - - // 2. Sync to DB - saveMutation.mutate({ body: newState }).catch((err) => logger.error('Failed to save tabs state:', err)) - }, - [mutateState, saveMutation] - ) + const tabs = useMemo(() => tabsState.tabs, [tabsState.tabs]) + const activeTabId = tabsState.activeTabId const addTab = useCallback( (tab: Tab) => { const exists = tabs.find((t) => t.id === tab.id) if (exists) { - updateState({ ...currentState, activeTabId: tab.id }) + setTabsState({ ...tabsState, activeTabId: tab.id }) return } const newTabs = [...tabs, tab] - updateState({ tabs: newTabs, activeTabId: tab.id }) + setTabsState({ tabs: newTabs, activeTabId: tab.id }) }, - [tabs, currentState, updateState] + [tabs, tabsState, setTabsState] ) const closeTab = useCallback( @@ -81,42 +31,49 @@ export function useTabs() { let newActiveId = activeTabId if (activeTabId === id) { - // Activate adjacent tab const index = tabs.findIndex((t) => t.id === id) - // Try to go left, then right const nextTab = newTabs[index - 1] || newTabs[index] newActiveId = nextTab ? nextTab.id : '' } - updateState({ tabs: newTabs, activeTabId: newActiveId }) + setTabsState({ tabs: newTabs, activeTabId: newActiveId }) }, - [tabs, activeTabId, updateState] + [tabs, activeTabId, setTabsState] ) const setActiveTab = useCallback( (id: string) => { if (id !== activeTabId) { - updateState({ ...currentState, activeTabId: id }) + setTabsState({ ...tabsState, activeTabId: id }) } }, - [activeTabId, currentState, updateState] + [activeTabId, tabsState, setTabsState] ) const updateTab = useCallback( (id: string, updates: Partial) => { const newTabs = tabs.map((t) => (t.id === id ? { ...t, ...updates } : t)) - updateState({ ...currentState, tabs: newTabs }) + setTabsState({ ...tabsState, tabs: newTabs }) }, - [tabs, currentState, updateState] + [tabs, tabsState, setTabsState] + ) + + const setTabs = useCallback( + (newTabs: Tab[] | ((prev: Tab[]) => Tab[])) => { + const resolvedTabs = typeof newTabs === 'function' ? newTabs(tabs) : newTabs + setTabsState({ ...tabsState, tabs: resolvedTabs }) + }, + [tabs, tabsState, setTabsState] ) return { tabs, activeTabId, - isLoading, + isLoading: false, addTab, closeTab, setActiveTab, - updateTab + updateTab, + setTabs } } diff --git a/src/renderer/src/routeTree.gen.ts b/src/renderer/src/routeTree.gen.ts deleted file mode 100644 index a9c8543e57..0000000000 --- a/src/renderer/src/routeTree.gen.ts +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -import { Route as rootRouteImport } from './routes/__root' -import { Route as IndexRouteImport } from './routes/index' - -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => rootRouteImport -} as any) - -export interface FileRoutesByFullPath { - '/': typeof IndexRoute -} -export interface FileRoutesByTo { - '/': typeof IndexRoute -} -export interface FileRoutesById { - __root__: typeof rootRouteImport - '/': typeof IndexRoute -} -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' - fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/' - fileRoutesById: FileRoutesById -} -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute -} - -declare module '@tanstack/react-router' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport - parentRoute: typeof rootRouteImport - } - } -} - -const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute -} -export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes()