@@ -7,14 +7,13 @@ import { fetchJSON } from '@/lib/api'
77 * Hook to sync theme between next-themes and backend settings.
88 * - On mount: applies saved theme preference from backend
99 * - Provides changeTheme function to update both next-themes and backend
10- * - Optionally syncs theme to Claude Code config when enabled
10+ * - Optionally syncs theme to Claude Code config when enabled (only on explicit user action)
1111 */
1212export function useThemeSync ( ) {
1313 const { setTheme, resolvedTheme, theme : currentTheme } = useNextTheme ( )
1414 const { data : savedTheme , isSuccess } = useTheme ( )
1515 const { data : syncClaudeCode } = useSyncClaudeCodeTheme ( )
1616 const updateConfig = useUpdateConfig ( )
17- const prevResolvedTheme = useRef < string | undefined > ( undefined )
1817 const prevSyncClaudeCode = useRef < boolean | undefined > ( undefined )
1918 const hasInitialized = useRef ( false )
2019
@@ -42,31 +41,23 @@ export function useThemeSync() {
4241 link . href = favicon
4342 } , [ resolvedTheme ] )
4443
45- // Sync to Claude Code when:
46- // 1. Resolved theme changes (if sync is enabled)
47- // 2. Sync setting is toggled on after initial load (immediate sync with current theme)
44+ // Sync to Claude Code when sync setting is toggled on (immediate sync with current theme)
45+ // NOTE: We intentionally do NOT sync on resolvedTheme changes here to avoid a feedback loop
46+ // when multiple tabs are open. Cross-tab theme sync is handled by next-themes via localStorage.
47+ // Claude sync only happens on explicit user action (changeTheme) or when enabling the toggle.
4848 useEffect ( ( ) => {
49- const themeChanged = resolvedTheme && resolvedTheme !== prevResolvedTheme . current
50-
51- // Only detect "just enabled" after initial render to avoid syncing on page load
5249 const syncJustEnabled = hasInitialized . current && syncClaudeCode && prevSyncClaudeCode . current === false
5350
54- // Update refs
55- prevResolvedTheme . current = resolvedTheme
5651 prevSyncClaudeCode . current = syncClaudeCode
5752 hasInitialized . current = true
5853
59- // Sync if theme changed while sync is enabled, or if sync was just enabled
60- if ( resolvedTheme && syncClaudeCode && ( themeChanged || syncJustEnabled ) ) {
61- // Fire and forget - no need to await
54+ if ( resolvedTheme && syncJustEnabled ) {
6255 fetchJSON ( '/api/config/sync-claude-theme' , {
6356 method : 'POST' ,
6457 body : JSON . stringify ( { resolvedTheme } ) ,
65- } ) . catch ( ( ) => {
66- // Silently ignore sync errors
67- } )
58+ } ) . catch ( ( ) => { } )
6859 }
69- } , [ resolvedTheme , syncClaudeCode ] )
60+ } , [ syncClaudeCode , resolvedTheme ] )
7061
7162 // Function to change theme and persist to backend
7263 const changeTheme = useCallback (
@@ -77,8 +68,19 @@ export function useThemeSync() {
7768 key : CONFIG_KEYS . THEME ,
7869 value : theme === 'system' ? '' : theme ,
7970 } )
71+
72+ // Sync to Claude Code if enabled (only on explicit user action, not cross-tab sync)
73+ if ( syncClaudeCode ) {
74+ const effectiveTheme = theme === 'system'
75+ ? ( window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ? 'dark' : 'light' )
76+ : theme
77+ fetchJSON ( '/api/config/sync-claude-theme' , {
78+ method : 'POST' ,
79+ body : JSON . stringify ( { resolvedTheme : effectiveTheme } ) ,
80+ } ) . catch ( ( ) => { } )
81+ }
8082 } ,
81- [ setTheme , updateConfig ]
83+ [ setTheme , updateConfig , syncClaudeCode ]
8284 )
8385
8486 return {
0 commit comments