diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index c1e0ccde0..9b31e36dd 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -4,9 +4,7 @@ import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2" import { Button } from "@opencode-ai/ui/button" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" -import { createMediaQuery } from "@solid-primitives/media" -import { useFile } from "@/context/file" import { useLayout } from "@/context/layout" import { useSync } from "@/context/sync" import { useLanguage } from "@/context/language" @@ -14,8 +12,6 @@ import { useProviders } from "@/hooks/use-providers" import { useSDK } from "@/context/sdk" import { getSessionContext } from "@/components/session/session-context-metrics" import { useSessionLayout } from "@/pages/session/session-layout" -import { createSessionTabs } from "@/pages/session/helpers" -import { useSettings } from "@/context/settings" interface SessionContextUsageProps { variant?: "button" | "indicator" @@ -45,23 +41,15 @@ function openSessionContext(args: { export function SessionContextUsage(props: SessionContextUsageProps) { const sync = useSync() - const file = useFile() const layout = useLayout() const language = useLanguage() const sdk = useSDK() - const settings = useSettings() const providers = useProviders(() => sdk().directory) const { params, tabs, view } = useSessionLayout() - const isDesktop = createMediaQuery("(min-width: 768px)") const variant = createMemo(() => props.variant ?? "button") const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default") - const tabState = createSessionTabs({ - tabs, - pathFromTab: file.pathFromTab, - normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab), - fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id, - }) + const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : [])) const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined)) @@ -77,7 +65,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { const cost = createMemo(() => { return usd().format(info()?.cost ?? 0) }) - const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context") + const contextVisible = createMemo(() => view().reviewPanel.opened() && tabs().active() === "context") const hasOtherTabs = createMemo(() => tabs() .all() diff --git a/packages/app/src/pages/session/helpers.test.ts b/packages/app/src/pages/session/helpers.test.ts index c04502562..548bc909c 100644 --- a/packages/app/src/pages/session/helpers.test.ts +++ b/packages/app/src/pages/session/helpers.test.ts @@ -10,6 +10,7 @@ import { getTabReorderIndex, shouldShowFileTree, } from "./helpers" +import { closeSessionTab, openSessionTab } from "@/context/layout-tabs" describe("shouldShowFileTree", () => { test("does not reserve space for a disabled file tree", () => { @@ -211,4 +212,66 @@ describe("createSessionTabs", () => { dispose() }) }) + + test("close context then re-open: activeTab returns context", () => { + // Step 1: context is open and active + const initial = { tabs: { all: ["context"], active: "context" as string | undefined }, preview: undefined } + + createRoot((dispose) => { + const tabs = createMemo(() => ({ active: () => initial.tabs.active, all: () => initial.tabs.all })) + const result = createSessionTabs({ + tabs, + pathFromTab: () => undefined, + normalizeTab: (tab) => tab, + review: () => true, + hasReview: () => true, + }) + expect(result.activeTab()).toBe("context") + expect(result.contextOpen()).toBe(true) + dispose() + }) + + // Step 2: close context — active falls to review + const afterClose = closeSessionTab(initial, "context") + expect(afterClose.tabs.all).toEqual([]) + expect(afterClose.tabs.active).toBeUndefined() + + createRoot((dispose) => { + const tabs = createMemo(() => ({ active: () => afterClose.tabs.active, all: () => afterClose.tabs.all })) + const result = createSessionTabs({ + tabs, + pathFromTab: () => undefined, + normalizeTab: (tab) => tab, + review: () => true, + hasReview: () => true, + }) + // Context is gone — activeTab falls to "review" via the hasReview fallback + expect(result.contextOpen()).toBe(false) + expect(result.activeTab()).toBe("review") + dispose() + }) + + // Step 3: re-open context — simulates openSessionContext calling tabs.open("context") + const afterReopen = openSessionTab( + { tabs: afterClose.tabs, preview: afterClose.preview }, + "context", + ) + expect(afterReopen.tabs.all).toContain("context") + expect(afterReopen.tabs.active).toBe("context") + + createRoot((dispose) => { + const tabs = createMemo(() => ({ active: () => afterReopen.tabs.active, all: () => afterReopen.tabs.all })) + const result = createSessionTabs({ + tabs, + pathFromTab: () => undefined, + normalizeTab: (tab) => tab, + review: () => true, + hasReview: () => true, + }) + // After re-open: activeTab MUST be "context" + expect(result.contextOpen()).toBe(true) + expect(result.activeTab()).toBe("context") + dispose() + }) + }) }) diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index bc13f5369..e04dff413 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -390,7 +390,7 @@ export function SessionSidePanel(props: { docs/adr/0001). Do not re-add a tab here: two hosts mirrored through two stores was the desync this column's toggle got blamed for. */} - +
{language.t("session.tab.context")}
-
+ {(tab) => ( @@ -598,7 +598,7 @@ export function SessionSidePanel(props: { : language.t("session.tab.review")} - +
{language.t("session.tab.context")}
-
+ {(tab) => (