diff --git a/packages/tui/src/feature-plugins/sidebar/context.tsx b/packages/tui/src/feature-plugins/sidebar/context.tsx index 7c11825eb7f..a94a89a188e 100644 --- a/packages/tui/src/feature-plugins/sidebar/context.tsx +++ b/packages/tui/src/feature-plugins/sidebar/context.tsx @@ -7,7 +7,7 @@ const money = new Intl.NumberFormat("en-US", { currency: "USD", }) -function View(props: { context: Plugin.Context; sessionID: string }) { +export function SidebarContext(props: { context: Plugin.Context; sessionID: string }) { const theme = props.context.theme const msg = createMemo(() => props.context.data.session.message.list(props.sessionID)) const session = createMemo(() => props.context.data.session.get(props.sessionID)) @@ -18,28 +18,32 @@ function View(props: { context: Plugin.Context; sessionID: string }) { ) return ( - - - Context - - Not measured}> - {(value) => ( - <> - {value().tokens.toLocaleString()} tokens - - {value().percent}% used - - - )} - - {money.format(cost())} spent - + 0}> + + + Context + + + {(value) => ( + <> + {value().tokens.toLocaleString()} tokens + + {value().percent}% used + + + )} + + 0}> + {money.format(cost())} spent + + + ) } export default Plugin.define({ id: "internal:sidebar-context", setup(context) { - context.ui.slot("sidebar.content", (props) => ) + context.ui.slot("sidebar.content", (props) => ) }, }) diff --git a/packages/tui/src/routes/session/sidebar.tsx b/packages/tui/src/routes/session/sidebar.tsx index eb8993dea74..8dedbf80154 100644 --- a/packages/tui/src/routes/session/sidebar.tsx +++ b/packages/tui/src/routes/session/sidebar.tsx @@ -27,9 +27,11 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { position={props.overlay ? "absolute" : "relative"} > queueMicrotask(() => scroll.verticalScrollBar.resetVisibilityControl())} flexGrow={1} scrollAcceleration={scrollAcceleration()} verticalScrollbarOptions={{ + visible: false, trackOptions: { backgroundColor: theme.background.default, foregroundColor: theme.scrollbar.default, diff --git a/packages/tui/test/feature-plugins/sidebar-context.test.tsx b/packages/tui/test/feature-plugins/sidebar-context.test.tsx new file mode 100644 index 00000000000..e984caaf968 --- /dev/null +++ b/packages/tui/test/feature-plugins/sidebar-context.test.tsx @@ -0,0 +1,70 @@ +/** @jsxImportSource @opentui/solid */ +import { expect, test } from "bun:test" +import { RGBA } from "@opentui/core" +import { testRender } from "@opentui/solid" +import type { Context } from "@opencode-ai/plugin/tui/context" +import { SidebarContext } from "../../src/feature-plugins/sidebar/context" + +function context(options?: { cost?: number; tokens?: number }) { + const color = RGBA.fromInts(200, 200, 200) + return { + theme: { text: { default: color, subdued: color } }, + data: { + session: { + get: () => ({ location: { directory: "/workspace" } }), + cost: () => options?.cost ?? 0, + message: { + list: () => + options?.tokens + ? [ + { + id: "message", + type: "assistant", + model: { providerID: "provider", id: "model" }, + tokens: { + input: options.tokens, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + }, + ] + : [], + }, + }, + location: { + model: { list: () => [] }, + }, + }, + } as unknown as Context +} + +test("sidebar omits context before usage is available", async () => { + const app = await testRender(() => , { + width: 42, + height: 8, + }) + + try { + await app.renderOnce() + expect(app.captureCharFrame()).not.toContain("Context") + expect(app.captureCharFrame()).not.toContain("Not measured") + } finally { + app.renderer.destroy() + } +}) + +test("sidebar shows available context usage", async () => { + const app = await testRender(() => , { + width: 42, + height: 8, + }) + + try { + await app.renderOnce() + expect(app.captureCharFrame()).toContain("Context") + expect(app.captureCharFrame()).toContain("1,234 tokens") + } finally { + app.renderer.destroy() + } +})