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()
+ }
+})