mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 17:29:53 +00:00
fix(tui): prevent sidebar scrollbar flash (#40056)
This commit is contained in:
parent
e872bd3c8f
commit
d5f6c088f0
3 changed files with 94 additions and 18 deletions
|
|
@ -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 (
|
||||
<box>
|
||||
<text fg={theme.text.default}>
|
||||
<b>Context</b>
|
||||
</text>
|
||||
<Show when={state()} fallback={<text fg={theme.text.subdued}>Not measured</text>}>
|
||||
{(value) => (
|
||||
<>
|
||||
<text fg={theme.text.subdued}>{value().tokens.toLocaleString()} tokens</text>
|
||||
<Show when={value().percent !== undefined}>
|
||||
<text fg={theme.text.subdued}>{value().percent}% used</text>
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<text fg={theme.text.subdued}>{money.format(cost())} spent</text>
|
||||
</box>
|
||||
<Show when={state() || cost() > 0}>
|
||||
<box>
|
||||
<text fg={theme.text.default}>
|
||||
<b>Context</b>
|
||||
</text>
|
||||
<Show when={state()}>
|
||||
{(value) => (
|
||||
<>
|
||||
<text fg={theme.text.subdued}>{value().tokens.toLocaleString()} tokens</text>
|
||||
<Show when={value().percent !== undefined}>
|
||||
<text fg={theme.text.subdued}>{value().percent}% used</text>
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={cost() > 0}>
|
||||
<text fg={theme.text.subdued}>{money.format(cost())} spent</text>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export default Plugin.define({
|
||||
id: "internal:sidebar-context",
|
||||
setup(context) {
|
||||
context.ui.slot("sidebar.content", (props) => <View context={context} sessionID={props.sessionID} />)
|
||||
context.ui.slot("sidebar.content", (props) => <SidebarContext context={context} sessionID={props.sessionID} />)
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
|||
position={props.overlay ? "absolute" : "relative"}
|
||||
>
|
||||
<scrollbox
|
||||
ref={(scroll) => queueMicrotask(() => scroll.verticalScrollBar.resetVisibilityControl())}
|
||||
flexGrow={1}
|
||||
scrollAcceleration={scrollAcceleration()}
|
||||
verticalScrollbarOptions={{
|
||||
visible: false,
|
||||
trackOptions: {
|
||||
backgroundColor: theme.background.default,
|
||||
foregroundColor: theme.scrollbar.default,
|
||||
|
|
|
|||
70
packages/tui/test/feature-plugins/sidebar-context.test.tsx
Normal file
70
packages/tui/test/feature-plugins/sidebar-context.test.tsx
Normal file
|
|
@ -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(() => <SidebarContext context={context()} sessionID="session" />, {
|
||||
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(() => <SidebarContext context={context({ tokens: 1234 })} sessionID="session" />, {
|
||||
width: 42,
|
||||
height: 8,
|
||||
})
|
||||
|
||||
try {
|
||||
await app.renderOnce()
|
||||
expect(app.captureCharFrame()).toContain("Context")
|
||||
expect(app.captureCharFrame()).toContain("1,234 tokens")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue