Skip to content

Commit 76f4582

Browse files
committed
feat(themes): add theme change reactivity to LOG_SYMBOLS
Make LOG_SYMBOLS automatically update when the fallback theme changes via setTheme(). The symbols now react to theme changes by updating their colors to match the new theme's color palette. Changes: - Added onThemeChange listener to LOG_SYMBOLS initialization - Split init into updateSymbols() and init() for reusability - Added reset() function that updates symbols on theme change - Removed Object.freeze() to allow symbol updates - Updated documentation to clarify LOG_SYMBOLS reflect fallback theme - Updated tests to verify theme change reactivity - Removed test expecting frozen symbols (no longer applicable) Note: LOG_SYMBOLS reflect the global fallback theme, not async-local theme contexts from withTheme(), as making them context-aware would require expensive AsyncLocalStorage lookups on every property access.
1 parent 6663174 commit 76f4582

2 files changed

Lines changed: 57 additions & 19 deletions

File tree

src/logger.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55

66
import isUnicodeSupported from './external/@socketregistry/is-unicode-supported'
77
import yoctocolorsCjs from './external/yoctocolors-cjs'
8-
import { objectAssign, objectFreeze } from './objects'
98
import { applyLinePrefix, isBlankString } from './strings'
109
import type { ColorValue } from './spinner'
11-
import { getTheme } from './themes/context'
10+
import { getTheme, onThemeChange } from './themes/context'
1211

1312
/**
1413
* Log symbols for terminal output with colored indicators.
@@ -143,8 +142,9 @@ function applyColor(
143142
* for terminals that don't support Unicode. Symbols are colored according to the active
144143
* theme's color palette (success, error, warning, info, step).
145144
*
146-
* The symbols are lazily initialized on first access and then frozen for immutability.
147-
* Colors are determined by the theme active at initialization time.
145+
* The symbols are lazily initialized on first access and automatically update when the
146+
* fallback theme changes (via setTheme()). Note that LOG_SYMBOLS reflect the global
147+
* fallback theme, not async-local theme contexts from withTheme().
148148
*
149149
* @example
150150
* ```typescript
@@ -161,11 +161,15 @@ export const LOG_SYMBOLS = /*@__PURE__*/ (() => {
161161
const target: Record<string, string> = {
162162
__proto__: null,
163163
} as unknown as Record<string, string>
164+
165+
let initialized = false
166+
164167
// Mutable handler to simulate a frozen target.
165168
const handler: ProxyHandler<Record<string, string>> = {
166169
__proto__: null,
167170
} as unknown as ProxyHandler<Record<string, string>>
168-
const init = () => {
171+
172+
const updateSymbols = () => {
169173
const supported = isUnicodeSupported()
170174
const colors = getYoctocolors()
171175
const theme = getTheme()
@@ -177,20 +181,38 @@ export const LOG_SYMBOLS = /*@__PURE__*/ (() => {
177181
const infoColor = theme.colors.info
178182
const stepColor = theme.colors.step
179183

180-
objectAssign(target, {
181-
fail: applyColor(supported ? '✖' : '×', errorColor, colors),
182-
info: applyColor(supported ? 'ℹ' : 'i', infoColor, colors),
183-
step: applyColor(supported ? '→' : '>', stepColor, colors),
184-
success: applyColor(supported ? '✔' : '√', successColor, colors),
185-
warn: applyColor(supported ? '⚠' : '‼', warningColor, colors),
186-
})
187-
objectFreeze(target)
184+
// Update symbol values
185+
target.fail = applyColor(supported ? '✖' : '×', errorColor, colors)
186+
target.info = applyColor(supported ? 'ℹ' : 'i', infoColor, colors)
187+
target.step = applyColor(supported ? '→' : '>', stepColor, colors)
188+
target.success = applyColor(supported ? '✔' : '√', successColor, colors)
189+
target.warn = applyColor(supported ? '⚠' : '‼', warningColor, colors)
190+
}
191+
192+
const init = () => {
193+
if (initialized) {
194+
return
195+
}
196+
197+
updateSymbols()
198+
initialized = true
199+
188200
// The handler of a Proxy is mutable after proxy instantiation.
189-
// We delete the traps to defer to native behavior.
201+
// We delete the traps to defer to native behavior for better performance.
190202
for (const trapName in handler) {
191203
delete handler[trapName as keyof ProxyHandler<Record<string, string>>]
192204
}
193205
}
206+
207+
const reset = () => {
208+
if (!initialized) {
209+
return
210+
}
211+
212+
// Update symbols with new theme colors
213+
updateSymbols()
214+
}
215+
194216
for (const trapName of Reflect.ownKeys(Reflect)) {
195217
const fn = (Reflect as Record<PropertyKey, unknown>)[trapName]
196218
if (typeof fn === 'function') {
@@ -202,6 +224,12 @@ export const LOG_SYMBOLS = /*@__PURE__*/ (() => {
202224
}
203225
}
204226
}
227+
228+
// Listen for theme changes and reset symbols
229+
onThemeChange(() => {
230+
reset()
231+
})
232+
205233
return new Proxy(target, handler)
206234
})()
207235

test/logger.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
incLogCallCountSymbol,
1010
lastWasBlankSymbol,
1111
} from '@socketsecurity/lib/logger'
12+
import { setTheme, THEMES } from '@socketsecurity/lib/themes'
1213

1314
describe('LOG_SYMBOLS', () => {
1415
it('should lazily initialize symbols', () => {
@@ -30,11 +31,20 @@ describe('LOG_SYMBOLS', () => {
3031
expect(step).toBeTruthy()
3132
})
3233

33-
it('should freeze symbols after initialization', () => {
34-
const symbols = LOG_SYMBOLS
35-
expect(() => {
36-
;(symbols as any).success = 'test'
37-
}).toThrow()
34+
it('should update symbols when theme changes', () => {
35+
// Initialize symbols with default theme
36+
const initialSuccess = LOG_SYMBOLS.success
37+
expect(initialSuccess).toBeTruthy()
38+
39+
// Change theme
40+
setTheme(THEMES.coana)
41+
42+
// Symbols should update
43+
const updatedSuccess = LOG_SYMBOLS.success
44+
expect(updatedSuccess).toBeTruthy()
45+
46+
// Reset to default theme for other tests
47+
setTheme(THEMES.socket)
3848
})
3949

4050
it('should be accessible via Logger.LOG_SYMBOLS', () => {

0 commit comments

Comments
 (0)