From 4d4db4281ee26128a7178bedfdb4670cb61613a1 Mon Sep 17 00:00:00 2001 From: Erosika Date: Thu, 2 Jul 2026 18:42:31 -0400 Subject: [PATCH] fix(desktop): submit only edited fields from the full-config modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unstored fields render their schema default, and 'Save all' persisted every one of them — pinning values that runtime defaults still own. Concretely: an existing Honcho user without an explicit observationMode runs 'unified' via the client's migration guard, but one untouched save-all flipped them to the schema's 'directional'. Diff against the seeded snapshot and submit only what the user changed. --- .../memory/provider-config-modal.test.tsx | 15 +++++---------- .../app/settings/memory/provider-config-modal.tsx | 13 ++++++++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx b/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx index 3fa8694a9647..7505dbb58064 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-modal.test.tsx @@ -80,20 +80,15 @@ describe('ProviderConfigModal', () => { expect(screen.getByDisplayValue('{"t":"eri"}')).toBeTruthy() }) - it('saves all fields, serializing the toggled bool to "false"', async () => { + it('saves only edited fields, serializing the toggled bool to "false"', async () => { const { onSaved, onOpenChange } = await renderModal() fireEvent.click(await screen.findByRole('switch')) - fireEvent.click(screen.getByRole('button', { name: 'Save all' })) + fireEvent.click(screen.getByRole('button', { name: 'Save changes' })) - await waitFor(() => - expect(saveMemoryProviderConfig).toHaveBeenCalledWith('honcho', { - workspace: 'myws', - saveMessages: 'false', - dialecticMaxChars: '1200', - userPeerAliases: '{"t":"eri"}' - }) - ) + // Untouched fields stay unsubmitted so a save never ratifies rendered + // defaults the backend does not actually store. + await waitFor(() => expect(saveMemoryProviderConfig).toHaveBeenCalledWith('honcho', { saveMessages: 'false' })) await waitFor(() => expect(onSaved).toHaveBeenCalled()) expect(onOpenChange).toHaveBeenCalledWith(false) }) diff --git a/apps/desktop/src/app/settings/memory/provider-config-modal.tsx b/apps/desktop/src/app/settings/memory/provider-config-modal.tsx index e6d072ecfa57..6bc9ae216137 100644 --- a/apps/desktop/src/app/settings/memory/provider-config-modal.tsx +++ b/apps/desktop/src/app/settings/memory/provider-config-modal.tsx @@ -53,20 +53,27 @@ export function ProviderConfigModal({ onSaved: () => Promise | void }) { const [values, setValues] = useState>({}) + const [seeded, setSeeded] = useState>({}) const [saving, setSaving] = useState(false) // Reseed from the latest config each time the dialog opens so edits never // start from a stale snapshot left over from a prior session. useEffect(() => { if (open) { - setValues(seedAll(config)) + const seed = seedAll(config) + setSeeded(seed) + setValues(seed) } }, [open, config]) const save = async () => { + // Unstored fields render their schema default; persisting untouched keys + // would pin values that runtime defaults (e.g. migration guards) still own. + const edited = Object.fromEntries(Object.entries(values).filter(([key, value]) => value !== seeded[key])) + setSaving(true) try { - await saveMemoryProviderConfig(provider, values) + await saveMemoryProviderConfig(provider, edited) notify({ kind: 'success', title: `${config.label} saved`, message: 'Memory provider configuration updated.' }) await onSaved() onOpenChange(false) @@ -138,7 +145,7 @@ export function ProviderConfigModal({