Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),
accentColor: z.string().optional(),
})

export type GlobalSettings = z.infer<typeof globalSettingsSchema>
Expand Down
4 changes: 4 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true)
// No need to call postStateToWebview here as the UI already updated optimistically
break
case "setAccentColor":
await updateGlobalState("accentColor", message.text ?? undefined)
await provider.postStateToWebview()
break
case "toggleApiConfigPin":
if (message.text) {
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export interface WebviewMessage {
| "profileThresholds"
| "setHistoryPreviewCollapsed"
| "setReasoningBlockCollapsed"
| "setAccentColor"
| "openExternal"
| "filterMarketplaceItems"
| "marketplaceButtonClicked"
Expand Down
13 changes: 13 additions & 0 deletions webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const App = () => {
cloudOrganizations,
renderContext,
mdmCompliant,
accentColor,
} = useExtensionState()

// Create a persistent state manager
Expand Down Expand Up @@ -233,6 +234,18 @@ const App = () => {
}
}, [renderContext]),
)

// Apply accent color to CSS custom property
useEffect(() => {
if (accentColor) {
document.documentElement.style.setProperty("--accent", accentColor)
document.documentElement.style.setProperty("--color-accent", accentColor)
} else {
// Reset to default when no accent color is set
document.documentElement.style.removeProperty("--accent")
document.documentElement.style.removeProperty("--color-accent")
}
}, [accentColor])
// Track marketplace tab views
useEffect(() => {
if (tab === "marketplace") {
Expand Down
1 change: 1 addition & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{activeTab === "ui" && (
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
accentColor={extensionState.accentColor}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accent color setting is not persisted correctly. Using extensionState.accentColor here bypasses the cached state system used by SettingsView, which means changes won't be included in the Save button's dirty state tracking and won't be persisted when clicking Save.

This should use accentColor from cachedState (destructured around line 201), and the handleSubmit function needs to send it via vscode.postMessage({ type: 'setAccentColor', text: accentColor }) to persist changes on Save.

Fix it with Roo Code or mention @roomote and request a fix.

setCachedStateField={setCachedStateField}
/>
)}
Expand Down
54 changes: 51 additions & 3 deletions webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HTMLAttributes } from "react"
import { HTMLAttributes, useState, useEffect } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { Glasses } from "lucide-react"
import { telemetryClient } from "@/utils/TelemetryClient"

Expand All @@ -11,11 +11,18 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"

interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
accentColor?: string
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...props }: UISettingsProps) => {
export const UISettings = ({ reasoningBlockCollapsed, accentColor, setCachedStateField, ...props }: UISettingsProps) => {
const { t } = useAppTranslation()
const [colorValue, setColorValue] = useState(accentColor || "")

// Sync local state with prop changes
useEffect(() => {
setColorValue(accentColor || "")
}, [accentColor])

const handleReasoningBlockCollapsedChange = (value: boolean) => {
setCachedStateField("reasoningBlockCollapsed", value)
Expand All @@ -26,6 +33,20 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
})
}

const handleAccentColorChange = (value: string) => {
setColorValue(value)
// Only update if it's a valid color or empty
const trimmedValue = value.trim()
if (trimmedValue === "" || /^#[0-9A-Fa-f]{6}$/.test(trimmedValue)) {
setCachedStateField("accentColor", trimmedValue || undefined)

// Track telemetry event
telemetryClient.capture("ui_settings_accent_color_changed", {
hasColor: trimmedValue !== "",
})
}
}

return (
<div {...props}>
<SectionHeader>
Expand All @@ -49,6 +70,33 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr
{t("settings:ui.collapseThinking.description")}
</div>
</div>

{/* Accent Color Setting */}
<div className="flex flex-col gap-2">
<label className="font-medium" htmlFor="accent-color-input">
Accent Color
</label>
<div className="flex items-center gap-2">
<VSCodeTextField
id="accent-color-input"
value={colorValue}
placeholder="#007ACC"
onInput={(e: any) => handleAccentColorChange(e.target.value)}
data-testid="accent-color-input"
style={{ flex: 1 }}
/>
<input
type="color"
value={colorValue && /^#[0-9A-Fa-f]{6}$/.test(colorValue) ? colorValue : "#007ACC"}
onChange={(e) => handleAccentColorChange(e.target.value)}
className="h-8 w-16 cursor-pointer rounded border border-vscode-input-border"
data-testid="accent-color-picker"
/>
</div>
<div className="text-vscode-descriptionForeground text-sm">
Customize the accent color used throughout the extension. Leave empty to use the default theme color. Enter a hex color code (e.g., #007ACC) or use the color picker.
</div>
</div>
</div>
</Section>
</div>
Expand Down
12 changes: 12 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setIncludeCurrentTime: (value: boolean) => void
includeCurrentCost?: boolean
setIncludeCurrentCost: (value: boolean) => void
accentColor?: string
setAccentColor: (value: string | undefined) => void
}

export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
Expand Down Expand Up @@ -298,6 +300,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
const [prevCloudIsAuthenticated, setPrevCloudIsAuthenticated] = useState(false)
const [includeCurrentTime, setIncludeCurrentTime] = useState(true)
const [includeCurrentCost, setIncludeCurrentCost] = useState(true)
const [accentColor, setAccentColor] = useState<string | undefined>(undefined)

const setListApiConfigMeta = useCallback(
(value: ProviderSettingsEntry[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
Expand Down Expand Up @@ -343,6 +346,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
if ((newState as any).includeCurrentCost !== undefined) {
setIncludeCurrentCost((newState as any).includeCurrentCost)
}
// Update accentColor if present in state message
if ((newState as any).accentColor !== undefined) {
setAccentColor((newState as any).accentColor)
}
// Handle marketplace data if present in state message
if (newState.marketplaceItems !== undefined) {
setMarketplaceItems(newState.marketplaceItems)
Expand Down Expand Up @@ -596,6 +603,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setIncludeCurrentTime,
includeCurrentCost,
setIncludeCurrentCost,
accentColor,
setAccentColor: (value: string | undefined) => {
setAccentColor(value)
vscode.postMessage({ type: "setAccentColor", text: value })
},
}

return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
Expand Down