22import { createEffect , createMemo , createSignal , on , onCleanup , untrack } from "solid-js"
33import type { TuiPluginInput } from "@opencode-ai/plugin/tui"
44import { Logger } from "../../lib/logger"
5- import { truncate } from "../../lib/ui/utils"
65import {
76 createPlaceholderContextSnapshot ,
87 invalidateContextSnapshot ,
@@ -13,11 +12,8 @@ import { getPalette, toneColor, type DcpColor, type DcpPalette } from "../shared
1312import { LABEL , type DcpRouteNames } from "../shared/names"
1413import type { DcpMessageStatus , DcpTuiClient } from "../shared/types"
1514
16- const BAR_WIDTH = 12
1715const SINGLE_BORDER = { type : "single" } as any
1816const 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
2218const 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