Compare commits

...

6 Commits

Author SHA1 Message Date
Kelly Yang
409b1d43ae Merge branch 'main' into fix/setting-store-deep-equality 2026-05-11 15:06:07 -07:00
Alexander Brown
660655a34b Merge branch 'main' into fix/setting-store-deep-equality 2026-05-07 01:22:56 -07:00
Alexander Brown
e5eecfbae7 Merge branch 'main' into fix/setting-store-deep-equality 2026-05-06 23:02:46 -07:00
Kelly Yang
49cf68db19 Revert "fix(keybindings): serialize KeybindingImpl to plain object before persisting"
This reverts commit 63fff48ce6.
2026-05-05 14:32:00 -07:00
Kelly Yang
63fff48ce6 fix(keybindings): serialize KeybindingImpl to plain object before persisting
_.isEqual compares constructors, so KeybindingImpl instances are never
equal to the plain objects returned by the backend. Serialize bindings
to plain objects before passing to setMany so the deep-equality check
in settingStore can correctly skip redundant writes.
2026-05-05 14:24:27 -07:00
Kelly Yang
55a558f323 fix(settings): use deep equality to skip redundant setting writes
`applySettingLocally` compared `newValue` and `oldValue` with `===`,
but `get()` always returns a `_.cloneDeep` result, so object and array
settings always produced different references even when the values were
identical. Replace with `_.isEqual` so redundant `onChange` calls and
API writes are correctly suppressed for non-primitive settings.
2026-05-05 13:57:05 -07:00
2 changed files with 43 additions and 1 deletions

View File

@@ -415,6 +415,48 @@ describe('useSettingStore', () => {
)
})
it('should not trigger onChange when setting the same object value', async () => {
const onChangeMock = vi.fn()
store.addSetting({
id: 'test.setting',
name: 'test.setting',
type: 'hidden',
defaultValue: {},
onChange: onChangeMock
})
vi.clearAllMocks()
await store.set('test.setting', { key: 'value', nested: { count: 1 } })
expect(onChangeMock).toHaveBeenCalledTimes(1)
expect(api.storeSetting).toHaveBeenCalledTimes(1)
// Same deep value in a new object reference — should be a no-op
await store.set('test.setting', { key: 'value', nested: { count: 1 } })
expect(onChangeMock).toHaveBeenCalledTimes(1)
expect(api.storeSetting).toHaveBeenCalledTimes(1)
})
it('should not trigger onChange when setting the same array value', async () => {
const onChangeMock = vi.fn()
store.addSetting({
id: 'test.setting',
name: 'test.setting',
type: 'hidden',
defaultValue: [],
onChange: onChangeMock
})
vi.clearAllMocks()
await store.set('test.setting', [1, 2, 3])
expect(onChangeMock).toHaveBeenCalledTimes(1)
expect(api.storeSetting).toHaveBeenCalledTimes(1)
// Same array contents in a new array reference — should be a no-op
await store.set('test.setting', [1, 2, 3])
expect(onChangeMock).toHaveBeenCalledTimes(1)
expect(api.storeSetting).toHaveBeenCalledTimes(1)
})
describe('object mutation prevention', () => {
beforeEach(() => {
const setting: SettingParams = {

View File

@@ -106,7 +106,7 @@ export const useSettingStore = defineStore('setting', () => {
clonedValue
)
const oldValue = get(key)
if (newValue === oldValue) return undefined
if (_.isEqual(newValue, oldValue)) return undefined
onChange(settingsById.value[key], newValue, oldValue)
settingValues.value[key] = newValue