Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions packages/app/src/components/session-context-usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@ 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"
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"
Expand Down Expand Up @@ -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))

Expand All @@ -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()
Expand Down
63 changes: 63 additions & 0 deletions packages/app/src/pages/session/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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()
})
})
})
8 changes: 4 additions & 4 deletions packages/app/src/pages/session/session-side-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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. */}
<Show when={contextOpen()}>
<div style={{ display: contextOpen() ? undefined : "none" }}>
<Tabs.Trigger
value="context"
closeButton={
Expand All @@ -417,7 +417,7 @@ export function SessionSidePanel(props: {
<div>{language.t("session.tab.context")}</div>
</div>
</Tabs.Trigger>
</Show>
</div>
<SortableProvider ids={openedTabs()}>
<For each={panelTabs()}>
{(tab) => (
Expand Down Expand Up @@ -598,7 +598,7 @@ export function SessionSidePanel(props: {
: language.t("session.tab.review")}
</Tabs.Trigger>
</Show>
<Show when={contextOpen()}>
<div style={{ display: contextOpen() ? undefined : "none" }}>
<Tabs.Trigger
value="context"
closeButton={
Expand Down Expand Up @@ -631,7 +631,7 @@ export function SessionSidePanel(props: {
<div>{language.t("session.tab.context")}</div>
</div>
</Tabs.Trigger>
</Show>
</div>
<For each={panelTabs()}>
{(tab) => (
<Show
Expand Down
Loading