Skip to content

fix(app): v2 tab bar does not refresh when session title is generated #30329

Closed

Description

@gzb1128

Description

In the v2 layout desktop/web app, when the LLM generates a session title, the session data is updated (visible in the sidebar session list), but the tab bar does not refresh to show the new title. The tab continues displaying the default title until the user navigates away or triggers a re-render.

Steps to reproduce

  1. Enable v2 layout design in settings (newLayoutDesigns)
  2. Start a new session (default title like "Session xxx" shown in both tab and sidebar)
  3. Send a message that triggers title generation
  4. Observe: the sidebar updates with the generated title, but the tab bar still shows the default title

Root Cause

The tab bar enrichment logic in packages/app/src/components/titlebar.tsx:444-455 uses mapArray to enrich tab data with session info:

const tabsEnriched = iife(() => {
  const base = mapArray(
    () => tabsStore,
    (tab) => {
      const sync = serverSync.createDirSyncContext(tab.dir)
      const session = sync.session.get(tab.sessionId)
      return session ? { ...tab, info: session } : null   // never accesses .title
    },
  )
  return () => base().flatMap((s) => (s ? [s] : []))
})

sync.session.get() calls getSession (packages/app/src/context/directory-sync.ts:202-207) which only tracks:

  • store.session.length
  • store.session[i].id (binary search comparisons)
  • store.session[match.index] (the proxy reference itself)

It does not track store.session[match.index].title. Since SolidJS store proxies use stable references (the proxy at a given index does not change when reconcile updates internal properties), the mapArray item memo never re-runs when the title changes.

The sidebar works correctly because it uses <For each={sessions()}> directly against the store array, where each session prop tracks .title reactively through the proxy.

Proposed Fix

Explicitly access session.title inside the mapArray mapper to create a reactive subscription:

 const tabsEnriched = iife(() => {
   const base = mapArray(
     () => tabsStore,
     (tab) => {
       const sync = serverSync.createDirSyncContext(tab.dir)
       const session = sync.session.get(tab.sessionId)
-      return session ? { ...tab, info: session } : null
+      return session ? { ...tab, info: session, title: session.title } : null
     },
   )
   return () => base().flatMap((s) => (s ? [s] : []))
 })

This ensures the mapArray item memo subscribes to store.session[index].title. When the title changes via session.updated SSE event → reconcile, the memo re-runs and the tab re-renders.

Operating System

macOS / all platforms

Activity

  1. gzb1128 commented on Jun 2, 2026

    @gzb1128
    Author

    Correction after checking SolidJS implementation: the original root-cause wording was too strong.

    mapArray itself runs the mapper inside untrack, so simply adding title: session.title inside that mapper would not create the subscription I described.

    A more accurate diagnosis is: the v2 tab bar resolves session info in a mapArray mapper that is only driven by the local tab list, while the title change comes from the external session store. Because the mapper is untracked, any session-derived snapshot created there can become stale. The robust fix is to keep only a session accessor in the mapper, then resolve info and read info.title inside the reactive tabsEnriched()/<For each> path:

    const base = mapArray(
      () => tabsStore,
      (tab) => {
        const sync = serverSync.createDirSyncContext(tab.dir)
        return { ...tab, info: () => sync.session.get(tab.sessionId) }
      },
    )
    
    return () =>
      base().flatMap((tab) => {
        const info = tab.info()
        return info ? [{ ...tab, info, title: info.title }] : []
      })

    This makes the each expression track info.title directly, so the tab bar reacts to session.updated events that change the generated title.

  2. opencode-agent commented on Jul 8, 2026

    @opencode-agent
    Contributor

    Verified against current v2: titlebar session tabs now read the reactive session store (sync.session.peek(...)), and TabNavItem tracks session.title, so session.updated title changes propagate to the tab. The old untracked mapArray enrichment path described here no longer exists. Closing as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

2.0bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions