fix(app): scope session tab indicators to the tab's server (#34861)

This commit is contained in:
Luke Parker 2026-07-02 16:07:35 +10:00 committed by GitHub
commit 1fb2ecc721
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 139 additions and 30 deletions

View file

@ -28,7 +28,6 @@ export function TabNavItem(props: {
onClose: () => void
onNavigate: () => void
active?: boolean
activeServer: boolean
forceTruncate?: boolean
suppressNavigation?: () => boolean
dragging?: boolean
@ -245,7 +244,7 @@ export function TabNavItem(props: {
project={project()}
directory={session().directory}
sessionId={session().id}
activeServer={props.activeServer}
server={props.server}
/>
</span>
)}

View file

@ -24,7 +24,6 @@ function SessionTabSlot(props: {
id: string
index: () => number
active: () => boolean
activeServerKey: ServerConnection.Key
forceTruncate: boolean
serverCtx: () => ServerCtx | undefined
onNavigate: (element: HTMLDivElement) => void
@ -112,7 +111,6 @@ function SessionTabSlot(props: {
onNavigate={() => props.onNavigate(ref)}
onClose={props.onClose}
active={props.active()}
activeServer={props.tab.server === props.activeServerKey}
forceTruncate={props.forceTruncate}
dragging={sortable.isDragSource()}
/>
@ -165,7 +163,6 @@ function DraftTabSlot(props: {
export function TitlebarTabStrip(props: {
tabs: Tab[]
currentTab: () => Tab | undefined
activeServerKey: ServerConnection.Key
forceTruncate: boolean
onNavigate: (tab: Tab, el?: HTMLDivElement) => void
onClose: (tab: Tab) => void
@ -271,7 +268,6 @@ export function TitlebarTabStrip(props: {
id={id}
index={index}
active={() => props.currentTab() === tab}
activeServerKey={props.activeServerKey}
forceTruncate={props.forceTruncate}
serverCtx={serverCtx}
onNavigate={(element) => {

View file

@ -456,7 +456,6 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
<TitlebarTabStrip
tabs={tabsStore}
currentTab={currentTab}
activeServerKey={server.key}
forceTruncate={tabsAreOverflowing()}
onOverflowChange={setTabsAreOverflowing}
onNavigate={(tab, el) => {

View file

@ -552,7 +552,6 @@ export function NewHome() {
results={searchResults()}
showProjectName={!selectedProject()}
server={selection().server}
activeServer={selection().server === server.key}
noResultsLabel={language.t("home.sessions.search.noResults", { query: search() })}
bindFocus={(focus) => {
focusSessionSearch = focus
@ -612,7 +611,6 @@ export function NewHome() {
record={record}
showProjectName={!selectedProject()}
server={selection().server}
activeServer={selection().server === server.key}
openSession={openSession}
archiveSession={archiveSession}
/>
@ -996,7 +994,6 @@ function HomeSessionLeading(props: {
project: LocalProject
session: Session
server: ServerConnection.Key
activeServer: boolean
revealProjectOnHover: boolean
}) {
const tabs = useTabs()
@ -1014,7 +1011,7 @@ function HomeSessionLeading(props: {
project={props.project}
directory={props.session.directory}
sessionId={props.session.id}
activeServer={props.activeServer}
server={props.server}
revealProjectOnHover={props.revealProjectOnHover}
/>
</div>
@ -1029,7 +1026,6 @@ function HomeSessionSearch(props: {
results: HomeSessionRecord[]
showProjectName: boolean
server: ServerConnection.Key
activeServer: boolean
noResultsLabel: string
bindFocus: (focus: () => void) => void
onInput: (value: string) => void
@ -1147,7 +1143,6 @@ function HomeSessionSearch(props: {
record={record}
showProjectName={props.showProjectName}
server={props.server}
activeServer={props.activeServer}
selected={store.active === homeSessionSearchKey(record)}
onHighlight={() => setStore("active", homeSessionSearchKey(record))}
onSelect={(session) => props.onSelect(session)}
@ -1228,7 +1223,6 @@ function HomeSessionSearchResultRow(props: {
record: HomeSessionRecord
showProjectName: boolean
server: ServerConnection.Key
activeServer: boolean
selected: boolean
onHighlight: () => void
onSelect: (session: Session) => void
@ -1258,7 +1252,6 @@ function HomeSessionSearchResultRow(props: {
project={props.record.project}
session={props.record.session}
server={props.server}
activeServer={props.activeServer}
revealProjectOnHover={!!showProjectName()}
/>
<div class="flex min-w-0 flex-1 items-center gap-1.5">
@ -1297,7 +1290,6 @@ function HomeSessionRow(props: {
record: HomeSessionRecord
showProjectName: boolean
server: ServerConnection.Key
activeServer: boolean
openSession: (session: Session) => void
archiveSession: (session: Session) => Promise<void>
}) {
@ -1320,7 +1312,6 @@ function HomeSessionRow(props: {
project={props.record.project}
session={props.record.session}
server={props.server}
activeServer={props.activeServer}
revealProjectOnHover={!!showProjectName()}
/>
<span

View file

@ -1,29 +1,41 @@
import { createMemo, type Accessor } from "solid-js"
import { useServerSync } from "@/context/server-sync"
import { useGlobal } from "@/context/global"
import { useNotification } from "@/context/notification"
import { usePermission } from "@/context/permission"
import { ServerConnection } from "@/context/server"
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
export function useSessionTabAvatarState(
server: Accessor<ServerConnection.Key>,
directory: Accessor<string>,
sessionId: Accessor<string>,
active: Accessor<boolean> = () => true,
) {
const globalSync = useServerSync()
const global = useGlobal()
const notification = useNotification()
const permission = usePermission()
const connection = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === server()))
const sync = createMemo(() => {
const conn = connection()
if (conn) return global.ensureServerCtx(conn).sync
})
const hasPermissions = createMemo(() => {
if (!active()) return false
const [store] = globalSync().child(directory(), { bootstrap: false })
return !!sessionPermissionRequest(store.session, globalSync().session.data.permission, sessionId(), (item) => {
const serverSync = sync()
if (!serverSync) return false
const [store] = serverSync.child(directory(), { bootstrap: false })
return !!sessionPermissionRequest(store.session, serverSync.session.data.permission, sessionId(), (item) => {
return !permission.autoResponds(item, directory())
})
})
const unread = createMemo(() => active() && (hasPermissions() || notification.session.unseenCount(sessionId()) > 0))
const unread = createMemo(() => {
if (hasPermissions()) return true
if (!connection()) return false
return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0
})
const loading = createMemo(() => {
if (!active()) return false
const serverSync = sync()
if (!serverSync) return false
if (hasPermissions()) return false
return globalSync().session.data.session_working(sessionId())
return serverSync.session.data.session_working(sessionId())
})
return { unread, loading }
}

View file

@ -1,5 +1,6 @@
import type { LocalProject } from "@/context/layout"
import { getProjectAvatarVariant } from "@/context/layout"
import type { ServerConnection } from "@/context/server"
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
import { useSessionTabAvatarState } from "@/pages/layout/project-avatar-state"
import { ProjectAvatar } from "@opencode-ai/ui/v2/project-avatar-v2"
@ -10,12 +11,14 @@ export function SessionTabAvatar(props: {
project?: LocalProject
directory: string
sessionId: string
activeServer: boolean
server: ServerConnection.Key
revealProjectOnHover?: boolean
}) {
const directory = () => props.directory
const sessionId = () => props.sessionId
const state = useSessionTabAvatarState(directory, sessionId, () => props.activeServer)
const state = useSessionTabAvatarState(
() => props.server,
() => props.directory,
() => props.sessionId,
)
const projectAvatar = () => (
<ProjectAvatar
fallback={displayName(props.project ?? { worktree: props.directory })}