Skip to content

Commit 54f25a3

Browse files
committed
fix(tui): use flexbox layout for sidebar bars to adapt to scrollbar width
1 parent efa3d95 commit 54f25a3

1 file changed

Lines changed: 28 additions & 46 deletions

File tree

tui/slots/sidebar-top.tsx

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { createEffect, createMemo, createSignal, on, onCleanup, untrack } from "solid-js"
33
import type { TuiPluginInput } from "@opencode-ai/plugin/tui"
44
import { Logger } from "../../lib/logger"
5-
import { truncate } from "../../lib/ui/utils"
65
import {
76
createPlaceholderContextSnapshot,
87
invalidateContextSnapshot,
@@ -13,11 +12,8 @@ import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared
1312
import { LABEL, type DcpRouteNames } from "../shared/names"
1413
import type { DcpMessageStatus, DcpTuiClient } from "../shared/types"
1514

16-
const BAR_WIDTH = 12
1715
const SINGLE_BORDER = { type: "single" } as any
1816
const DIM_TEXT = { dim: true } as any
19-
// Content width derived from graph row: label(9) + space(1) + percent(4) + " |"(2) + bar(12) + "| "(2) + tokens(~5)
20-
const CONTENT_WIDTH = 9 + 1 + 4 + 2 + BAR_WIDTH + 2 + 5
2117

2218
const REFRESH_DEBOUNCE_MS = 100
2319

@@ -37,37 +33,17 @@ const compactTokenCount = (value: number): string => {
3733
return "0"
3834
}
3935

40-
const buildBar = (value: number, total: number) => {
41-
if (total <= 0) return " ".repeat(BAR_WIDTH)
42-
const filled = Math.max(0, Math.round((value / total) * BAR_WIDTH))
43-
return "█".repeat(filled).padEnd(BAR_WIDTH, " ")
44-
}
45-
46-
const buildMessageBar = (
36+
const buildMessageRuns = (
4737
statuses: DcpMessageStatus[],
48-
width: number = CONTENT_WIDTH,
49-
): { text: string; status: DcpMessageStatus }[] => {
50-
const ACTIVE = "█"
51-
const PRUNED = "░"
52-
if (statuses.length === 0) return [{ text: PRUNED.repeat(width), status: "pruned" }]
53-
54-
// Map each bar position to a message status
55-
const bar: DcpMessageStatus[] = new Array(width).fill("active")
56-
for (let m = 0; m < statuses.length; m++) {
57-
const start = Math.floor((m / statuses.length) * width)
58-
const end = Math.floor(((m + 1) / statuses.length) * width)
59-
for (let i = start; i < end; i++) {
60-
bar[i] = statuses[m]
61-
}
62-
}
38+
): { count: number; status: DcpMessageStatus }[] => {
39+
if (statuses.length === 0) return [{ count: 1, status: "pruned" }]
6340

64-
// Group consecutive same-status positions into runs
65-
const runs: { text: string; status: DcpMessageStatus }[] = []
41+
// Group consecutive same-status messages into runs
42+
const runs: { count: number; status: DcpMessageStatus }[] = []
6643
let runStart = 0
67-
for (let i = 1; i <= width; i++) {
68-
if (i === width || bar[i] !== bar[runStart]) {
69-
const char = bar[runStart] === "pruned" ? PRUNED : ACTIVE
70-
runs.push({ text: char.repeat(i - runStart), status: bar[runStart] })
44+
for (let i = 1; i <= statuses.length; i++) {
45+
if (i === statuses.length || statuses[i] !== statuses[runStart]) {
46+
runs.push({ count: i - runStart, status: statuses[runStart] })
7147
runStart = i
7248
}
7349
}
@@ -111,17 +87,24 @@ const SidebarContextBar = (props: {
11187
props.total > 0 ? `${Math.round((props.value / props.total) * 100)}%` : "0%",
11288
)
11389
const label = createMemo(() => props.label.padEnd(9, " "))
114-
const bar = createMemo(() => buildBar(props.value, props.total))
11590
return (
116-
<box flexDirection="row">
91+
<box width="100%" flexDirection="row">
11792
<text fg={props.palette.text}>
11893
{label()}
11994
{` ${percent().padStart(4, " ")} |`}
12095
</text>
121-
<text fg={toneColor(props.palette, props.tone)}>{bar()}</text>
122-
<text
123-
fg={props.palette.text}
124-
>{`| ${compactTokenCount(props.value).padStart(5, " ")}`}</text>
96+
<box flexGrow={1} flexDirection="row" height={1}>
97+
{props.value > 0 && (
98+
<box
99+
flexGrow={props.value}
100+
backgroundColor={toneColor(props.palette, props.tone)}
101+
/>
102+
)}
103+
{props.total > props.value && <box flexGrow={props.total - props.value} />}
104+
</box>
105+
<text fg={props.palette.text}>
106+
{`| ${compactTokenCount(props.value).padStart(5, " ")}`}
107+
</text>
125108
</box>
126109
)
127110
}
@@ -299,7 +282,7 @@ const SidebarContext = (props: {
299282
const topicOverflow = createMemo(() => topicTotal() - topics().length)
300283
const fallbackNote = createMemo(() => snapshot().notes[0] ?? "")
301284

302-
const messageBarRuns = createMemo(() => buildMessageBar(snapshot().messageStatuses))
285+
const messageBarRuns = createMemo(() => buildMessageRuns(snapshot().messageStatuses))
303286

304287
const status = createMemo(() => {
305288
if (error() && snapshot().breakdown.total > 0)
@@ -351,15 +334,14 @@ const SidebarContext = (props: {
351334
/>
352335

353336
{snapshot().messageStatuses.length > 0 && (
354-
<box flexDirection="row" marginTop={1}>
337+
<box width="100%" flexDirection="row" height={1} marginTop={1}>
355338
{messageBarRuns().map((run) => (
356-
<text
357-
fg={
339+
<box
340+
flexGrow={run.count}
341+
backgroundColor={
358342
run.status === "active" ? props.palette.accent : props.palette.muted
359343
}
360-
>
361-
{run.text}
362-
</text>
344+
/>
363345
))}
364346
</box>
365347
)}
@@ -402,7 +384,7 @@ const SidebarContext = (props: {
402384
<b>Compressed Topics</b>
403385
</text>
404386
{topics().map((t) => (
405-
<text fg={props.palette.muted}>{truncate(t, CONTENT_WIDTH)}</text>
387+
<text fg={props.palette.muted}>{t}</text>
406388
))}
407389
{topicOverflow() > 0 ? (
408390
<text {...DIM_TEXT} fg={props.palette.muted}>

0 commit comments

Comments
 (0)