فهرست منبع

fix(app): persist session tab info (#34934)

Brendan Allan 2 ماه پیش
والد
کامیت
64fd7a2027

+ 9 - 6
packages/app/src/components/titlebar-tab-nav.tsx

@@ -246,18 +246,21 @@ export function TabNavItem(props: {
           }}
           class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
         >
-          <Show when={props.session()}>
-            {(session) => (
-              <span data-slot="project-avatar-slot">
+          <span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
+            <Show
+              when={props.session()}
+              fallback={<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />}
+            >
+              {(session) => (
                 <SessionTabAvatar
                   project={project()}
                   directory={session().directory}
                   sessionId={session().id}
                   server={props.server}
                 />
-              </span>
-            )}
-          </Show>
+              )}
+            </Show>
+          </span>
           <span
             ref={(el) => {
               titleEl = el

+ 6 - 3
packages/app/src/components/titlebar-tab-strip.tsx

@@ -40,6 +40,7 @@ function SessionTabSlot(props: {
   let ref!: HTMLDivElement
   const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
   const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
+  const persisted = createMemo(() => tabs.info[props.id])
   const [loadedSession] = createResource(
     () => {
       const ctx = props.serverCtx()
@@ -71,8 +72,10 @@ function SessionTabSlot(props: {
 
   createEffect(() => {
     const value = session()
+    if (!value) return
+    tabs.rememberSessionInfo(props.tab, value)
     const current = sdk()
-    if (!value || !current) return
+    if (!current) return
     createTabPromptState(tabs, props.tab, current.scope, {
       dir: base64Encode(value.directory),
       id: value.id,
@@ -86,7 +89,7 @@ function SessionTabSlot(props: {
       data-tab-key={props.id}
       data-active={props.active()}
       class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
-      classList={{ hidden: !session() && !missingSession() }}
+      classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
     >
       <TabNavItem
         ref={(el) => {
@@ -95,7 +98,7 @@ function SessionTabSlot(props: {
         href={tabHref(props.tab)}
         server={props.tab.server}
         session={session}
-        fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
+        fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
         onTitleChange={(title) => {
           const value = session()
           const ctx = props.serverCtx()

+ 35 - 2
packages/app/src/context/tabs.tsx

@@ -28,6 +28,11 @@ export type DraftTab = {
 
 export type Tab = SessionTab | DraftTab
 
+export type TabInfo = {
+  title?: string
+  directory?: string
+}
+
 type RecentTab = {
   key?: string
 }
@@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       createStore<Tab[]>([]),
     )
     const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
+    const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
     const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
 
     const params = useParams()
@@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
     }
 
+    const removeInfo = (key: string) => {
+      if (!info[key]) return
+      setInfo(
+        produce((draft) => {
+          delete draft[key]
+        }),
+      )
+    }
+
     onCleanup(memory.dispose)
 
     createEffect(() => {
@@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       const next = store.filter((tab) => servers.has(tab.server))
       if (next.length !== store.length) {
         for (const tab of store) {
-          if (!servers.has(tab.server)) memory.remove(tabKey(tab))
+          if (!servers.has(tab.server)) {
+            const key = tabKey(tab)
+            memory.remove(key)
+            removeInfo(key)
+          }
         }
         setStore(() => next)
       }
       if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
+      const keys = new Set(next.map(tabKey))
+      for (const key of Object.keys(info)) {
+        if (!keys.has(key)) removeInfo(key)
+      }
     })
 
     createEffect(() => {
@@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
         if (nextTab) navigateTab(nextTab)
       }).finally(() => closing.delete(key))
       memory.remove(key)
+      removeInfo(key)
       if (draftID) removeDraftPersisted(draftID)
     }
 
@@ -264,6 +288,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
         const removed = store.filter((tab) => tab.server === key).map(tabKey)
         setStore((tabs) => tabs.filter((tab) => tab.server !== key))
         for (const key of removed) memory.remove(key)
+        for (const key of removed) removeInfo(key)
         if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
         for (const draftID of drafts) removeDraftPersisted(draftID)
         if (server.key === key) navigate("/")
@@ -318,6 +343,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
           if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
         })
         for (const key of removed) memory.remove(key)
+        for (const key of removed) removeInfo(key)
+      },
+      rememberSessionInfo(tab: SessionTab, session: Session) {
+        const key = tabKey(tab)
+        const next = { title: session.title, directory: session.directory }
+        const current = info[key]
+        if (current?.title === next.title && current.directory === next.directory) return
+        setInfo(key, next)
       },
       select: navigateTab,
       remember(tab: Tab) {
@@ -342,6 +375,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
       },
     }
 
-    return { ...actions, store, ready, recentReady }
+    return { ...actions, store, info, ready, recentReady }
   },
 })