@@ -16,6 +16,7 @@ function requireFetchCall(fetchMock: ReturnType<typeof vi.fn>, index = 0) {
1616describe ( "loadControlUiBootstrapConfig" , ( ) => {
1717 afterEach ( ( ) => {
1818 setUiTimeFormatPreference ( "auto" ) ;
19+ document . documentElement . removeAttribute ( "style" ) ;
1920 } ) ;
2021
2122 it ( "threads agents.defaults.timeFormat into the UI hour-cycle preference" , async ( ) => {
@@ -108,6 +109,101 @@ describe("loadControlUiBootstrapConfig", () => {
108109 vi . unstubAllGlobals ( ) ;
109110 } ) ;
110111
112+ it ( "applies configured seamColor to Control UI accent variables" , async ( ) => {
113+ const fetchMock = vi . fn ( ) . mockResolvedValue ( {
114+ ok : true ,
115+ json : async ( ) => ( {
116+ basePath : "" ,
117+ assistantName : "Main" ,
118+ assistantAvatar : "M" ,
119+ assistantAgentId : "main" ,
120+ seamColor : "#1A2b3C" ,
121+ } ) ,
122+ } ) ;
123+ vi . stubGlobal ( "fetch" , fetchMock as unknown as typeof fetch ) ;
124+
125+ const state = {
126+ basePath : "" ,
127+ assistantName : "Assistant" ,
128+ assistantAvatar : null ,
129+ assistantAvatarSource : null ,
130+ assistantAvatarStatus : null ,
131+ assistantAvatarReason : null ,
132+ assistantAgentId : null ,
133+ localMediaPreviewRoots : [ ] ,
134+ embedSandboxMode : "scripts" as const ,
135+ allowExternalEmbedUrls : false ,
136+ chatMessageMaxWidth : null ,
137+ serverVersion : null ,
138+ } ;
139+
140+ await loadControlUiBootstrapConfig ( state ) ;
141+
142+ const rootStyle = document . documentElement . style ;
143+ expect ( rootStyle . getPropertyValue ( "--accent" ) ) . toBe ( "#1A2b3C" ) ;
144+ expect ( rootStyle . getPropertyValue ( "--ring" ) ) . toBe ( "#1A2b3C" ) ;
145+ expect ( rootStyle . getPropertyValue ( "--primary" ) ) . toBe ( "#1A2b3C" ) ;
146+ expect ( rootStyle . getPropertyValue ( "--accent-hover" ) ) . toBe (
147+ "color-mix(in srgb, var(--accent) 82%, white 18%)" ,
148+ ) ;
149+ expect ( rootStyle . getPropertyValue ( "--accent-subtle" ) ) . toBe (
150+ "color-mix(in srgb, var(--accent) 16%, transparent)" ,
151+ ) ;
152+
153+ vi . unstubAllGlobals ( ) ;
154+ } ) ;
155+
156+ it ( "removes server seamColor variables when bootstrap color is missing or invalid" , async ( ) => {
157+ const fetchMock = vi
158+ . fn ( )
159+ . mockResolvedValueOnce ( {
160+ ok : true ,
161+ json : async ( ) => ( {
162+ basePath : "" ,
163+ assistantName : "Main" ,
164+ assistantAvatar : "M" ,
165+ assistantAgentId : "main" ,
166+ seamColor : "00aaee" ,
167+ } ) ,
168+ } )
169+ . mockResolvedValueOnce ( {
170+ ok : true ,
171+ json : async ( ) => ( {
172+ basePath : "" ,
173+ assistantName : "Main" ,
174+ assistantAvatar : "M" ,
175+ assistantAgentId : "main" ,
176+ seamColor : "lobster" ,
177+ } ) ,
178+ } ) ;
179+ vi . stubGlobal ( "fetch" , fetchMock as unknown as typeof fetch ) ;
180+
181+ const state = {
182+ basePath : "" ,
183+ assistantName : "Assistant" ,
184+ assistantAvatar : null ,
185+ assistantAvatarSource : null ,
186+ assistantAvatarStatus : null ,
187+ assistantAvatarReason : null ,
188+ assistantAgentId : null ,
189+ localMediaPreviewRoots : [ ] ,
190+ embedSandboxMode : "scripts" as const ,
191+ allowExternalEmbedUrls : false ,
192+ chatMessageMaxWidth : null ,
193+ serverVersion : null ,
194+ } ;
195+
196+ await loadControlUiBootstrapConfig ( state ) ;
197+ expect ( document . documentElement . style . getPropertyValue ( "--accent" ) ) . toBe ( "#00aaee" ) ;
198+
199+ await loadControlUiBootstrapConfig ( state ) ;
200+ expect ( document . documentElement . style . getPropertyValue ( "--accent" ) ) . toBe ( "" ) ;
201+ expect ( document . documentElement . style . getPropertyValue ( "--ring" ) ) . toBe ( "" ) ;
202+ expect ( document . documentElement . style . getPropertyValue ( "--focus-ring" ) ) . toBe ( "" ) ;
203+
204+ vi . unstubAllGlobals ( ) ;
205+ } ) ;
206+
111207 it ( "can refresh runtime bootstrap settings without clobbering session identity" , async ( ) => {
112208 const fetchMock = vi . fn ( ) . mockResolvedValue ( {
113209 ok : true ,
0 commit comments