mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-25 10:21:55 +00:00
fix(app): preserve subagent tab lineage (#44404)
This commit is contained in:
parent
1def4aa35a
commit
4c786f0ff8
10 changed files with 242 additions and 28 deletions
|
|
@ -26,6 +26,32 @@ test("navigates to a subagent child session missing from the session list", asyn
|
|||
await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1)
|
||||
})
|
||||
|
||||
test("shows parent lineage while the child timeline loads", async ({ page }) => {
|
||||
await setup(page)
|
||||
const requested = Promise.withResolvers<void>()
|
||||
const release = Promise.withResolvers<void>()
|
||||
await page.route(
|
||||
(url) =>
|
||||
url.pathname === `/api/session/${childID}/message` &&
|
||||
url.port === (process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"),
|
||||
async (route) => {
|
||||
requested.resolve()
|
||||
await release.promise
|
||||
await route.fallback()
|
||||
},
|
||||
)
|
||||
|
||||
await page.goto(sessionHref(parentID))
|
||||
await expectSessionTitle(page, parentTitle)
|
||||
await page.locator(`a[href="${sessionHref(childID)}"]`).click()
|
||||
await Promise.all([requested.promise, expect(page).toHaveURL(sessionHref(childID))])
|
||||
await Promise.all([
|
||||
expect(page.locator('[data-slot="session-title-parent"]')).toHaveText(parentTitle),
|
||||
expect(page.locator('[data-slot="session-title-child"]')).toHaveText(childTitle),
|
||||
]).finally(() => release.resolve())
|
||||
await expectSessionTitle(page, taskDescription)
|
||||
})
|
||||
|
||||
test("keeps the parent visible while the child session resolves", async ({ page }) => {
|
||||
await setup(page)
|
||||
const requested = Promise.withResolvers<void>()
|
||||
|
|
@ -50,6 +76,44 @@ test("keeps the parent visible while the child session resolves", async ({ page
|
|||
await expectSessionTitle(page, taskDescription)
|
||||
})
|
||||
|
||||
test("keeps the parent tab selected while a loaded child session resolves", async ({ page }) => {
|
||||
await setup(page)
|
||||
await openChildFromParent(page)
|
||||
await expectSessionTitle(page, taskDescription)
|
||||
await page.goBack()
|
||||
await Promise.all([expect(page).toHaveURL(sessionHref(parentID)), expectSessionTitle(page, parentTitle)])
|
||||
|
||||
const requested = Promise.withResolvers<void>()
|
||||
const release = Promise.withResolvers<void>()
|
||||
await page.route(
|
||||
(url) => url.pathname === `/api/session/${childID}` && url.port === (process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"),
|
||||
async (route) => {
|
||||
requested.resolve()
|
||||
await release.promise
|
||||
await route.fallback()
|
||||
},
|
||||
)
|
||||
|
||||
const parentTab = page.locator("[data-titlebar-tab-slot]", {
|
||||
has: page.locator('[data-slot="tab-title"]', { hasText: parentTitle }),
|
||||
})
|
||||
await page.locator(`a[href="${sessionHref(childID)}"]`).click()
|
||||
await Promise.all([requested.promise, expect(page).toHaveURL(sessionHref(childID))])
|
||||
await Promise.all([
|
||||
expect(parentTab).toHaveAttribute("data-active", "true"),
|
||||
expect(page.locator('[data-slot="session-title-parent"]')).toHaveText(parentTitle),
|
||||
]).finally(() => release.resolve())
|
||||
await expectSessionTitle(page, taskDescription)
|
||||
|
||||
const home = page.getByRole("button", { name: "Home" })
|
||||
await home.click()
|
||||
await expect(page).toHaveURL("/")
|
||||
const childTab = page.locator(`[data-slot="titlebar-tabs"] a[href="${sessionHref(childID)}"]`)
|
||||
await expect(childTab).toHaveCount(1)
|
||||
await childTab.click()
|
||||
await Promise.all([expect(page).toHaveURL(sessionHref(childID)), expectSessionTitle(page, taskDescription)])
|
||||
})
|
||||
|
||||
test("shows the not found fallback when the viewed session is deleted", async ({ page }) => {
|
||||
const events: OpenCodeEvent[] = []
|
||||
await setup(page, () => events.splice(0, 1))
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export function ActiveSessionComposerRegion(props: {
|
|||
}) {
|
||||
const region = createSessionComposerRegionController({
|
||||
state: props.model.region.state,
|
||||
sessionID: () => props.session.identity.params.id,
|
||||
parentID: props.session.data.parentID,
|
||||
centered: props.model.region.centered,
|
||||
onResponseSubmit: props.onResponseSubmit,
|
||||
openParent: props.model.region.openParent,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
import { type Accessor, createMemo } from "solid-js"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import type { Accessor } from "solid-js"
|
||||
import type { SessionRequestModel } from "../requests/model"
|
||||
|
||||
export function createSessionComposerRegionController(input: {
|
||||
state: SessionRequestModel
|
||||
sessionID: Accessor<string | undefined>
|
||||
parentID: Accessor<string | undefined>
|
||||
centered: Accessor<boolean>
|
||||
onResponseSubmit: () => void
|
||||
openParent: () => void
|
||||
setPromptRef: (el: HTMLDivElement) => void
|
||||
setDockRef: (el: HTMLDivElement) => void
|
||||
}) {
|
||||
const data = useData()
|
||||
const parentID = createMemo(() => {
|
||||
const id = input.sessionID()
|
||||
return id ? data.session.get(id)?.parentID : undefined
|
||||
})
|
||||
return {
|
||||
state: input.state,
|
||||
centered: input.centered,
|
||||
|
|
@ -23,9 +17,9 @@ export function createSessionComposerRegionController(input: {
|
|||
openParent: input.openParent,
|
||||
setPromptRef: input.setPromptRef,
|
||||
setDockRef: input.setDockRef,
|
||||
parentID,
|
||||
child: () => !!parentID(),
|
||||
showComposer: () => !input.state.blocked() || !!parentID(),
|
||||
parentID: input.parentID,
|
||||
child: () => !!input.parentID(),
|
||||
showComposer: () => !input.state.blocked() || !!input.parentID(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {
|
|||
} from "./session-domain"
|
||||
import { useSessionLayout } from "./session-layout"
|
||||
import { createSessionOwnership } from "./session-ownership"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
import { useServer } from "@/runtime/server/current"
|
||||
|
||||
const emptyMessages: SessionMessageInfo[] = []
|
||||
const emptyUserMessages: SessionMessageUser[] = []
|
||||
|
|
@ -23,6 +25,8 @@ const idle = { type: "idle" as const }
|
|||
export function useSessionModel() {
|
||||
const file = useFile()
|
||||
const data = useData()
|
||||
const server = useServer()
|
||||
const shellTabs = useTabs()
|
||||
const layout = useSessionLayout()
|
||||
const location = useWorkspaceLocation()
|
||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||
|
|
@ -31,7 +35,16 @@ export function useSessionModel() {
|
|||
const id = sessionID()
|
||||
return id ? data.session.get(id) : undefined
|
||||
})
|
||||
const parentID = createMemo(() => info()?.parentID)
|
||||
const parentID = createMemo(() => {
|
||||
const current = info()?.parentID
|
||||
if (current) return current
|
||||
const id = sessionID()
|
||||
if (!id) return
|
||||
const tab = shellTabs.store.find(
|
||||
(item) => item.type === "session" && item.server === server.key && item.routeSessionId === id,
|
||||
)
|
||||
return tab?.type === "session" ? (tab.routeParentId ?? tab.sessionId) : undefined
|
||||
})
|
||||
const parent = createMemo(() => {
|
||||
const id = parentID()
|
||||
return id ? data.session.get(id) : undefined
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { SessionInfo } from "@opencode-ai/client/promise"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { ProjectAvatar } from "@opencode-ai/ui/project-avatar"
|
||||
import { useNavigate } from "@solidjs/router"
|
||||
import { createMemo, Show, type ParentProps } from "solid-js"
|
||||
import { useServer } from "@/runtime/server/current"
|
||||
import { displayName, getProjectAvatarSource, projectForSession } from "@/shell/layout/helpers"
|
||||
|
|
@ -9,6 +10,7 @@ import { tabKey, useTabs } from "@/shell/tabs/tabs"
|
|||
import { useSettings } from "@/settings/model"
|
||||
import { pathKey } from "@/workspaces/path-key"
|
||||
import { isWorkspaceDirectory } from "@/workspaces/paths"
|
||||
import { sessionHref } from "@/shell/routes/session"
|
||||
import { sessionTitle } from "./title"
|
||||
|
||||
export function SessionTitleHeader(props: ParentProps) {
|
||||
|
|
@ -26,11 +28,36 @@ export function SessionIdentityHeader(props: { sessionID: string; session?: Sess
|
|||
const server = useServer()
|
||||
const tabs = useTabs()
|
||||
const settings = useSettings()
|
||||
const info = createMemo(
|
||||
() => tabs.info[tabKey({ type: "session", server: server.key, sessionId: props.sessionID })],
|
||||
const navigate = useNavigate()
|
||||
const tab = createMemo(() =>
|
||||
tabs.store.find(
|
||||
(item) =>
|
||||
item.type === "session" &&
|
||||
item.server === server.key &&
|
||||
(item.sessionId === props.sessionID || item.routeSessionId === props.sessionID),
|
||||
),
|
||||
)
|
||||
const info = createMemo(() => {
|
||||
const current = tab()
|
||||
return current ? tabs.info[tabKey(current)] : undefined
|
||||
})
|
||||
const parentID = createMemo(() => {
|
||||
if (props.session?.parentID) return props.session.parentID
|
||||
const current = tab()
|
||||
if (current?.type !== "session" || current.routeSessionId !== props.sessionID) return
|
||||
return current.routeParentId ?? current.sessionId
|
||||
})
|
||||
const parent = createMemo(() => {
|
||||
const id = parentID()
|
||||
return id ? server.ctx.data.session.get(id) : undefined
|
||||
})
|
||||
const parentTitle = createMemo(() => {
|
||||
const id = parentID()
|
||||
const current = tab()
|
||||
return sessionTitle(parent()?.title ?? (current?.type === "session" && current.sessionId === id ? info()?.title : undefined))
|
||||
})
|
||||
const directory = createMemo(() => props.session?.location.directory ?? info()?.directory)
|
||||
const title = createMemo(() => sessionTitle(props.session?.title ?? info()?.title))
|
||||
const title = createMemo(() => sessionTitle(props.session?.title ?? (parentID() ? undefined : info()?.title)))
|
||||
const project = createMemo(() => {
|
||||
const projects = server.ctx.projects.list()
|
||||
if (props.session) return projectForSession(props.session, projects)
|
||||
|
|
@ -44,9 +71,16 @@ export function SessionIdentityHeader(props: { sessionID: string; session?: Sess
|
|||
const showProjectIcon = () =>
|
||||
import.meta.env.VITE_OPENCODE_CHANNEL !== "prod" && settings.general.showProjectIcon() && !!directory()
|
||||
const workspaceSession = createMemo(() => isWorkspaceDirectory(project(), directory() ?? ""))
|
||||
const navigateParent = () => {
|
||||
const id = parentID()
|
||||
const current = tab()
|
||||
if (!id || current?.type !== "session") return
|
||||
tabs.rememberSessionRoute(current, id, parent()?.parentID)
|
||||
navigate(sessionHref(server.key, id))
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={title() || showProjectIcon()}>
|
||||
<Show when={title() || parentTitle() || showProjectIcon()}>
|
||||
<SessionTitleHeader>
|
||||
<div class="flex h-12 w-full items-center justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-1">
|
||||
|
|
@ -69,9 +103,32 @@ export function SessionIdentityHeader(props: { sessionID: string; session?: Sess
|
|||
/>
|
||||
</Show>
|
||||
</span>
|
||||
<Show when={parentTitle()}>
|
||||
{(value) => (
|
||||
<button
|
||||
type="button"
|
||||
data-slot="session-title-parent"
|
||||
dir="auto"
|
||||
class="min-w-0 max-w-[40%] truncate pl-2 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-faint transition-colors hover:text-v2-text-text-muted"
|
||||
onClick={navigateParent}
|
||||
>
|
||||
{value()}
|
||||
</button>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={parentTitle() && title()}>
|
||||
<span
|
||||
data-slot="session-title-separator"
|
||||
class="-translate-y-[0.5px] pl-2 pr-1 text-[11px] font-medium text-v2-text-text-faint"
|
||||
aria-hidden="true"
|
||||
>
|
||||
/
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={title()}>
|
||||
{(value) => (
|
||||
<h1
|
||||
data-slot={parentID() ? "session-title-child" : undefined}
|
||||
dir="auto"
|
||||
class="w-fit truncate rounded-[6px] px-2 py-1 text-[13px] font-[530] leading-4 tracking-[-0.04px] text-v2-text-text-base"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { LocalProvider } from "@/providers/models/selection"
|
|||
import type { ServerConnection } from "@/runtime/server/registry"
|
||||
import { sessionHref } from "@/shell/routes/session"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
|
||||
export function SessionUIProvider(
|
||||
props: ParentProps<{
|
||||
|
|
@ -15,9 +16,17 @@ export function SessionUIProvider(
|
|||
const navigate = useNavigate()
|
||||
const params = useParams()
|
||||
const data = useData()
|
||||
const tabs = useTabs()
|
||||
const directory = () => props.directory
|
||||
const href = (sessionID: string) => sessionHref(props.server, sessionID)
|
||||
const navigateToSession = async (sessionID: string) => {
|
||||
const tab = tabs.store.find(
|
||||
(item) =>
|
||||
item.type === "session" &&
|
||||
item.server === props.server &&
|
||||
(item.sessionId === params.id || item.routeSessionId === params.id),
|
||||
)
|
||||
if (tab?.type === "session") tabs.rememberSessionRoute(tab, sessionID, params.id)
|
||||
await data.session.sync(sessionID).catch(() => undefined)
|
||||
navigate(href(sessionID))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,25 @@ export function migrateTabs(value: unknown): Tab[] {
|
|||
if (!tab || typeof tab !== "object") return []
|
||||
if (!("server" in tab) || typeof tab.server !== "string") return []
|
||||
const server = tab.server as ServerConnection.Key
|
||||
if (tab.type === "session" && typeof tab.sessionId === "string") {
|
||||
return [{ type: tab.type, server, sessionId: tab.sessionId }]
|
||||
if (
|
||||
tab.type === "session" &&
|
||||
typeof tab.sessionId === "string" &&
|
||||
(tab.routeSessionId === undefined || typeof tab.routeSessionId === "string") &&
|
||||
(tab.routeParentId === undefined || typeof tab.routeParentId === "string")
|
||||
) {
|
||||
return [
|
||||
{
|
||||
type: tab.type,
|
||||
server,
|
||||
sessionId: tab.sessionId,
|
||||
...(tab.routeSessionId && tab.routeSessionId !== tab.sessionId
|
||||
? {
|
||||
routeSessionId: tab.routeSessionId,
|
||||
...(tab.routeParentId ? { routeParentId: tab.routeParentId } : {}),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
]
|
||||
}
|
||||
if (
|
||||
tab.type === "draft" &&
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
|
|||
import { createRoot, getOwner, onCleanup } from "solid-js"
|
||||
import { createTabMemory } from "./memory"
|
||||
import { nextTabAfterClose, pushClosedTab, removeClosedTabs, takeClosedTab, type ClosedTab } from "./closed"
|
||||
import type { SessionTab, Tab } from "./tabs"
|
||||
import { tabHref, tabKey, type SessionTab, type Tab } from "./tabs"
|
||||
import { migrateTabs } from "./migration"
|
||||
import type { ServerConnection } from "@/runtime/server/registry"
|
||||
|
||||
|
|
@ -27,6 +27,24 @@ describe("tab migration", () => {
|
|||
expect(migrateTabs(null)).toEqual([])
|
||||
expect(migrateTabs({})).toEqual([])
|
||||
})
|
||||
|
||||
test("preserves the active child route", () => {
|
||||
expect(migrateTabs([{ ...sessionTab("root"), routeSessionId: "child", routeParentId: "parent" }])).toEqual([
|
||||
{ ...sessionTab("root"), routeSessionId: "child", routeParentId: "parent" },
|
||||
])
|
||||
})
|
||||
|
||||
test("drops an invalid child route", () => {
|
||||
expect(migrateTabs([{ ...sessionTab("parent"), routeSessionId: 1 }])).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
test("session tab identity stays rooted while its href follows the child route", () => {
|
||||
const parent = sessionTab("parent")
|
||||
const child = { ...parent, routeSessionId: "child" }
|
||||
|
||||
expect(tabKey(child)).toBe(tabKey(parent))
|
||||
expect(tabHref(child)).toContain("/session/child")
|
||||
})
|
||||
|
||||
describe("tab memory", () => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export type SessionTab = {
|
|||
type: "session"
|
||||
server: ServerConnection.Key
|
||||
sessionId: string
|
||||
routeSessionId?: string
|
||||
routeParentId?: string
|
||||
}
|
||||
|
||||
export type DraftTab = {
|
||||
|
|
@ -43,12 +45,18 @@ type RecentTab = {
|
|||
export const draftHref = (draftID: string) => `/new-session?draftId=${encodeURIComponent(draftID)}`
|
||||
|
||||
export const tabHref = (tab: Tab) =>
|
||||
tab.type === "draft" ? draftHref(tab.draftID) : sessionHref(tab.server, tab.sessionId)
|
||||
tab.type === "draft" ? draftHref(tab.draftID) : sessionHref(tab.server, tab.routeSessionId ?? tab.sessionId)
|
||||
|
||||
export const tabKey = (tab: Tab) => (tab.type === "draft" ? `draft:${tab.draftID}` : `${tab.server}\n${tabHref(tab)}`)
|
||||
export const tabKey = (tab: Tab) =>
|
||||
tab.type === "draft" ? `draft:${tab.draftID}` : `${tab.server}\n${sessionHref(tab.server, tab.sessionId)}`
|
||||
|
||||
export function sessionHasOpenTab(tabs: Tab[], server: ServerConnection.Key, session: SessionInfo) {
|
||||
return tabs.some((tab) => tab.type === "session" && tab.server === server && tab.sessionId === session.id)
|
||||
return tabs.some(
|
||||
(tab) =>
|
||||
tab.type === "session" &&
|
||||
tab.server === server &&
|
||||
(tab.sessionId === session.id || tab.routeSessionId === session.id),
|
||||
)
|
||||
}
|
||||
|
||||
export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
||||
|
|
@ -284,7 +292,10 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
removeSessionTab(input: Omit<SessionTab, "type">) {
|
||||
updateClosed((stack) => removeClosedTabs(stack, input.server, [input.sessionId]))
|
||||
const index = store.findIndex(
|
||||
(tab) => tab.type === "session" && tab.server === input.server && tab.sessionId === input.sessionId,
|
||||
(tab) =>
|
||||
tab.type === "session" &&
|
||||
tab.server === input.server &&
|
||||
(tab.sessionId === input.sessionId || tab.routeSessionId === input.sessionId),
|
||||
)
|
||||
if (index !== -1) removeTab(index)
|
||||
},
|
||||
|
|
@ -360,6 +371,18 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||
const key = tabKey(tab)
|
||||
if (recentKey() !== key) setRecentKey(key)
|
||||
},
|
||||
rememberSessionRoute(tab: SessionTab, sessionId: string, parentId?: string) {
|
||||
const index = store.findIndex((item) => tabKey(item) === tabKey(tab))
|
||||
if (index === -1) return
|
||||
setStore(
|
||||
index,
|
||||
produce((item) => {
|
||||
if (item.type !== "session") return
|
||||
item.routeSessionId = sessionId === item.sessionId ? undefined : sessionId
|
||||
item.routeParentId = sessionId === item.sessionId ? undefined : parentId
|
||||
}),
|
||||
)
|
||||
},
|
||||
toggleHome(input: { home: boolean; current?: Tab }) {
|
||||
if (input.home) {
|
||||
const tab = store.find((tab) => tabKey(tab) === recentKey())
|
||||
|
|
|
|||
|
|
@ -151,15 +151,24 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||
const tabs = useTabs()
|
||||
const tabsStore = tabs.store
|
||||
const tabsStoreActions = tabs
|
||||
const [session] = createResource(
|
||||
const [loadedSession] = createResource(
|
||||
() => {
|
||||
const route = layout.route()
|
||||
if (route.type !== "session") return undefined
|
||||
const conn = global.servers.list().find((item) => ServerConnection.key(item) === route.server)
|
||||
return conn ? { route, sdk: global.ensureServerCtx(conn).sdk } : undefined
|
||||
return conn ? { route, ctx: global.ensureServerCtx(conn) } : undefined
|
||||
},
|
||||
({ route, sdk }) => sdk.api.session.get({ sessionID: route.sessionId }).catch(() => {}),
|
||||
({ route, ctx }) => ctx.sdk.api.session.get({ sessionID: route.sessionId }).catch(() => {}),
|
||||
)
|
||||
const session = createMemo(() => {
|
||||
const route = layout.route()
|
||||
if (route.type !== "session") return
|
||||
const conn = global.servers.list().find((item) => ServerConnection.key(item) === route.server)
|
||||
const cached = conn ? global.ensureServerCtx(conn).data.session.get(route.sessionId) : undefined
|
||||
if (cached) return cached
|
||||
const loaded = loadedSession()
|
||||
return loaded?.id === route.sessionId ? loaded : undefined
|
||||
})
|
||||
|
||||
const matchRoute = (route: LayoutRoute) => {
|
||||
if (route.type === "home") return
|
||||
|
|
@ -169,7 +178,9 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||
if (route.type === "session") {
|
||||
const main = tabsStore.find(
|
||||
(item) =>
|
||||
item.type === "session" && item.server === route.server && item.sessionId === route.sessionId,
|
||||
item.type === "session" &&
|
||||
item.server === route.server &&
|
||||
(item.sessionId === route.sessionId || item.routeSessionId === route.sessionId),
|
||||
)
|
||||
if (main) return main
|
||||
const s = session()
|
||||
|
|
@ -190,6 +201,14 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||
if (!tabs.ready()) return
|
||||
const tab = currentTab()
|
||||
if (tab) {
|
||||
const current = session()
|
||||
if (
|
||||
route.type === "session" &&
|
||||
tab.type === "session" &&
|
||||
(route.sessionId === tab.sessionId || current?.id === route.sessionId)
|
||||
) {
|
||||
tabs.rememberSessionRoute(tab, route.sessionId, current?.parentID)
|
||||
}
|
||||
tabs.remember(tab)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue