@@ -31,7 +31,12 @@ import { formatQueuedPreview } from './utils/helpers'
3131import { loadLocalAgents } from './utils/local-agent-registry'
3232import { logger } from './utils/logger'
3333import { buildMessageTree } from './utils/message-tree-utils'
34- import { chatTheme , createMarkdownPalette } from './utils/theme-system'
34+ import {
35+ chatTheme ,
36+ createMarkdownPalette ,
37+ onThemeChange ,
38+ type ChatTheme ,
39+ } from './utils/theme-system'
3540
3641import type { User } from './utils/auth'
3742import type { ToolName } from '@codebuff/sdk'
@@ -127,8 +132,47 @@ export const App = ({
127132 const terminalWidth = resolvedTerminalWidth
128133 const separatorWidth = Math . max ( 1 , Math . floor ( terminalWidth ) - 2 )
129134
130- const theme = chatTheme
131- const markdownPalette = useMemo ( ( ) => createMarkdownPalette ( theme ) , [ theme ] )
135+ const cloneTheme = ( input : ChatTheme ) : ChatTheme => ( {
136+ ...input ,
137+ markdown : input . markdown
138+ ? {
139+ ...input . markdown ,
140+ headingFg : input . markdown . headingFg
141+ ? { ...input . markdown . headingFg }
142+ : undefined ,
143+ }
144+ : undefined ,
145+ } )
146+
147+ const [ theme , setTheme ] = useState < ChatTheme > ( ( ) => cloneTheme ( chatTheme ) )
148+ const [ resolvedThemeName , setResolvedThemeName ] = useState < 'dark' | 'light' > (
149+ chatTheme . messageTextAttributes ? 'dark' : 'light' ,
150+ )
151+
152+ useEffect ( ( ) => {
153+ const unsubscribe = onThemeChange ( ( updatedTheme , meta ) => {
154+ const nextTheme = cloneTheme ( updatedTheme )
155+ setTheme ( nextTheme )
156+ setResolvedThemeName ( meta . resolvedThemeName )
157+ if ( process . env . CODEBUFF_THEME_DEBUG === '1' ) {
158+ logger . debug (
159+ {
160+ themeChange : {
161+ source : meta . source ,
162+ resolvedThemeName : meta . resolvedThemeName ,
163+ } ,
164+ } ,
165+ 'Applied theme change in chat component' ,
166+ )
167+ }
168+ } )
169+ return unsubscribe
170+ } , [ ] )
171+
172+ const markdownPalette = useMemo (
173+ ( ) => createMarkdownPalette ( theme ) ,
174+ [ theme ] ,
175+ )
132176
133177 const [ exitWarning , setExitWarning ] = useState < string | null > ( null )
134178 const exitArmedRef = useRef ( false )
@@ -191,16 +235,27 @@ export const App = ({
191235 )
192236 } , [ ] )
193237
194- // Initialize with loaded agents message
238+ // Initialize and update loaded agents message when theme changes
195239 useEffect ( ( ) => {
196- if ( loadedAgentsData && messages . length === 0 ) {
197- const agentListId = 'loaded-agents-list'
198- const userCredentials = getUserCredentials ( )
199- const greeting = userCredentials ?. name ?. trim ( ) . length
200- ? `Welcome back, ${ userCredentials . name . trim ( ) } !`
201- : null
202-
203- const blocks : ContentBlock [ ] = [
240+ if ( ! loadedAgentsData ) {
241+ return
242+ }
243+
244+ const agentListId = 'loaded-agents-list'
245+ const userCredentials = getUserCredentials ( )
246+ const greeting = userCredentials ?. name ?. trim ( ) . length
247+ ? `Welcome back, ${ userCredentials . name . trim ( ) } !`
248+ : null
249+
250+ const baseTextColor =
251+ resolvedThemeName === 'dark'
252+ ? '#ffffff'
253+ : theme . chromeText && theme . chromeText !== 'default'
254+ ? theme . chromeText
255+ : theme . agentResponseCount
256+
257+ const buildBlocks = ( listId : string ) : ContentBlock [ ] => {
258+ const result : ContentBlock [ ] = [
204259 {
205260 type : 'text' ,
206261 content : '\n\n' + LOGO_BLOCK ,
@@ -209,41 +264,76 @@ export const App = ({
209264 ]
210265
211266 if ( greeting ) {
212- blocks . push ( {
267+ result . push ( {
213268 type : 'text' ,
214269 content : greeting ,
215- color : theme . agentResponseCount ,
270+ color : baseTextColor ,
216271 } )
217272 }
218273
219- blocks . push (
274+ result . push (
220275 {
221276 type : 'text' ,
222277 content :
223278 'Codebuff can read and write files in this repository, and run terminal commands to help you build.' ,
224- color : theme . agentResponseCount ,
279+ color : baseTextColor ,
225280 } ,
226281 {
227282 type : 'agent-list' ,
228- id : agentListId ,
283+ id : listId ,
229284 agents : loadedAgentsData . agents ,
230285 agentsDir : loadedAgentsData . agentsDir ,
231286 } ,
232287 )
233288
289+ return result
290+ }
291+
292+ if ( messages . length === 0 ) {
293+ const initialBlocks = buildBlocks ( agentListId )
234294 const initialMessage : ChatMessage = {
235295 id : `system-loaded-agents-${ Date . now ( ) } ` ,
236296 variant : 'ai' ,
237297 content : '' , // Content is in the block
238- blocks,
298+ blocks : initialBlocks ,
239299 timestamp : new Date ( ) . toISOString ( ) ,
240300 }
241301
242- // Set as collapsed by default
243302 setCollapsedAgents ( ( prev ) => new Set ( [ ...prev , agentListId ] ) )
244303 setMessages ( [ initialMessage ] )
304+ return
245305 }
246- } , [ loadedAgentsData , theme ] ) // Only run when loadedAgentsData changes
306+
307+ setMessages ( ( prev ) => {
308+ if ( prev . length === 0 ) {
309+ return prev
310+ }
311+
312+ const [ firstMessage , ...rest ] = prev
313+ if ( ! firstMessage . blocks ) {
314+ return prev
315+ }
316+
317+ const agentListBlock = firstMessage . blocks . find (
318+ ( block ) : block is Extract < ContentBlock , { type : 'agent-list' } > =>
319+ block . type === 'agent-list' ,
320+ )
321+
322+ if ( ! agentListBlock ) {
323+ return prev
324+ }
325+
326+ const updatedBlocks = buildBlocks ( agentListBlock . id )
327+
328+ return [
329+ {
330+ ...firstMessage ,
331+ blocks : updatedBlocks ,
332+ } ,
333+ ...rest ,
334+ ]
335+ } )
336+ } , [ loadedAgentsData , resolvedThemeName , theme ] )
247337
248338 const {
249339 inputValue,
0 commit comments