From cddc6a2c406a5884f3bea264f9c20eb192aa419d Mon Sep 17 00:00:00 2001 From: jearhe <0verme7753@gmail.com> Date: Tue, 18 Aug 2026 10:50:47 +0800 Subject: [PATCH] fix(settings): report apply failures without stalling --- .../editor/EditorSettingsDialog.vue | 27 ++++++++++-- .../EditorSettingsDialog.applyError.spec.ts | 42 +++++++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 1 + apps/desktop/src/i18n/locales/es.ts | 1 + apps/desktop/src/i18n/locales/it.ts | 1 + apps/desktop/src/i18n/locales/ja.ts | 1 + apps/desktop/src/i18n/locales/ko.ts | 1 + apps/desktop/src/i18n/locales/pt-BR.ts | 1 + apps/desktop/src/i18n/locales/zh-CN.ts | 1 + apps/desktop/src/i18n/locales/zh-TW.ts | 1 + 10 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.applyError.spec.ts diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 55425b0a18..27210c9329 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -984,13 +984,34 @@ async function persistSettings() { } } +function applySettingsErrorToast(error: unknown) { + toast(t("settings.applyFailed", { error: error instanceof Error ? error.message : String(error) }), 5000); +} + +// Shared apply entrypoint used by both "Apply" and "Apply & Close". Persists the +// current draft and reports failure explicitly instead of silently swallowing it +// (persistSettings() has several awaited persistence steps with no try/catch, so a +// rejected save used to leave "Apply" silent and made "Apply & Close" unclosable with +// no feedback). Returns whether persistence succeeded so the close path only proceeds +// once the settings are actually saved. +async function applySettingsForResult(): Promise { + try { + await persistSettings(); + return true; + } catch (error) { + applySettingsErrorToast(error); + return false; + } +} + async function applySettings() { - await persistSettings(); + await applySettingsForResult(); } async function applySettingsAndClose() { - await persistSettings(); - closeSettings(); + if (await applySettingsForResult()) { + closeSettings(); + } } async function restartDbxForDuckDbIsolation() { diff --git a/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.applyError.spec.ts b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.applyError.spec.ts new file mode 100644 index 0000000000..94b937d2fe --- /dev/null +++ b/apps/desktop/src/components/editor/__tests__/EditorSettingsDialog.applyError.spec.ts @@ -0,0 +1,42 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const dialogSource = readFileSync(new URL("../EditorSettingsDialog.vue", import.meta.url), "utf8"); + +// Regression for https://github.com/t8y2/dbx/issues/6485: applying editor +// settings (e.g. the editor font) could appear to "do nothing" because +// persistSettings() has several awaited persistence steps with no error +// handling. On a rejected save, "Apply" was completely silent and "Apply & +// Close" never called closeSettings(), leaving the dialog stuck open with no +// feedback at all. +describe("EditorSettingsDialog apply persistence error handling", () => { + it("shares a single apply entrypoint between Apply and Apply & Close", () => { + // Both buttons must go through one shared persist/apply routine instead of + // each calling persistSettings() directly with divergent semantics. + const block = dialogSource.slice(dialogSource.indexOf("function applySettingsErrorToast"), dialogSource.indexOf("async function restartDbxForDuckDbIsolation()")); + const entrypointCalls = (block.match(/await applySettingsForResult\(\)/g) || []).length; + // one call in applySettings() + one (guarded) call in applySettingsAndClose() + expect(entrypointCalls).toBeGreaterThanOrEqual(2); + // the shared entrypoint exists and wraps persistSettings + expect(block).toContain("async function applySettingsForResult(): Promise"); + expect(block).toContain("await persistSettings();"); + }); + + it("never closes the dialog before persistence succeeds", () => { + // "Apply & Close" must only proceed to closeSettings() after a successful + // apply. Before the fix, closeSettings() ran unconditionally after + // `await persistSettings()`, so a rejected save left the dialog stuck open. + const block = dialogSource.slice(dialogSource.indexOf("async function applySettingsAndClose()"), dialogSource.indexOf("async function restartDbxForDuckDbIsolation()")); + expect(block).toMatch(/if \(await applySettingsForResult\(\)\) \{\s*\n\s*closeSettings\(\);/); + }); + + it("surfaces persistence failures instead of silently swallowing them", () => { + const resultBlock = dialogSource.slice(dialogSource.indexOf("async function applySettingsForResult()"), dialogSource.indexOf("async function applySettings()")); + expect(resultBlock).toContain("try {"); + expect(resultBlock).toContain("catch (error)"); + expect(resultBlock).toContain("applySettingsErrorToast(error)"); + expect(resultBlock).toContain("return false;"); + // a user-facing message is wired up (and translated in every locale) + expect(dialogSource).toContain('toast(t("settings.applyFailed"'); + }); +}); diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 90e5a11e98..8744d516a3 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -5822,6 +5822,7 @@ export default { duckDbWorkerProcessIsolationRestartRequired: "Restart DBX for this change to take effect.", restartDbx: "Restart DBX", restartDbxFailed: "Failed to restart DBX: {error}", + applyFailed: "Failed to apply settings: {error}", unsavedChangesCloseTitle: "Unsaved changes", unsavedChangesCloseMessage: "You have unsaved settings changes that will be lost if you close now. Discard these changes?", unsavedChangesCloseCancel: "Keep editing", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 306b223231..ce31f77235 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -5563,6 +5563,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "Reinicia DBX para que este cambio surta efecto.", restartDbx: "Reiniciar DBX", restartDbxFailed: "No se pudo reiniciar DBX: {error}", + applyFailed: "No se pudieron aplicar los ajustes: {error}", unsavedChangesCloseTitle: "Cambios sin guardar", unsavedChangesCloseMessage: "Tienes cambios de configuración sin guardar que se perderán si cierras ahora. ¿Descartar estos cambios?", unsavedChangesCloseCancel: "Seguir editando", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 08b06b0c30..70bda8628d 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -5563,6 +5563,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "Riavvia DBX affinché questa modifica abbia effetto.", restartDbx: "Riavvia DBX", restartDbxFailed: "Impossibile riavviare DBX: {error}", + applyFailed: "Impossibile applicare le impostazioni: {error}", unsavedChangesCloseTitle: "Modifiche non salvate", unsavedChangesCloseMessage: "Ci sono modifiche alle impostazioni non salvate che andranno perse se chiudi ora. Vuoi scartare queste modifiche?", unsavedChangesCloseCancel: "Continua a modificare", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index fa4f404008..22860cd9a9 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -5598,6 +5598,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "この変更を有効にするには DBX を再起動してください。", restartDbx: "DBX を再起動", restartDbxFailed: "DBX の再起動に失敗しました: {error}", + applyFailed: "設定の適用に失敗しました: {error}", unsavedChangesCloseTitle: "未保存の変更", unsavedChangesCloseMessage: "未保存の設定変更があります。今すぐ閉じると失われます。これらの変更を破棄しますか?", unsavedChangesCloseCancel: "編集を続ける", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 81fae18809..a0fd96221a 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -5291,6 +5291,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "이 변경 사항을 적용하려면 DBX를 다시 시작하세요.", restartDbx: "DBX 다시 시작", restartDbxFailed: "DBX 다시 시작 실패: {error}", + applyFailed: "설정 적용 실패: {error}", unsavedChangesCloseTitle: "저장하지 않은 변경 사항", unsavedChangesCloseMessage: "저장하지 않은 설정 변경 사항이 있습니다. 지금 닫으면 사라집니다. 이 변경 사항을 취소하시겠습니까?", unsavedChangesCloseCancel: "계속 편집", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 037f06761a..af557dc640 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -5565,6 +5565,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "Reinicie o DBX para que esta alteração entre em vigor.", restartDbx: "Reiniciar DBX", restartDbxFailed: "Falha ao reiniciar o DBX: {error}", + applyFailed: "Falha ao aplicar as configurações: {error}", unsavedChangesCloseTitle: "Alterações não salvas", unsavedChangesCloseMessage: "Você tem alterações de configuração não salvas que serão perdidas se fechar agora. Descartar essas alterações?", unsavedChangesCloseCancel: "Continuar editando", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 9d845bbd94..8e30ba7977 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -5808,6 +5808,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "重启 DBX 后生效。", restartDbx: "重启 DBX", restartDbxFailed: "重启 DBX 失败:{error}", + applyFailed: "应用设置失败:{error}", unsavedChangesCloseTitle: "未保存的更改", unsavedChangesCloseMessage: "当前设置有未保存的更改,关闭后将丢失。是否放弃这些更改?", unsavedChangesCloseCancel: "继续编辑", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index b7e6d3287f..01e67dae96 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -4899,6 +4899,7 @@ export default withEnglishFallback({ duckDbWorkerProcessIsolationRestartRequired: "重啟 DBX 後生效。", restartDbx: "重啟 DBX", restartDbxFailed: "重啟 DBX 失敗:{error}", + applyFailed: "套用設定失敗:{error}", unsavedChangesCloseTitle: "未儲存的變更", unsavedChangesCloseMessage: "目前設定有未儲存的變更,關閉後將遺失。是否放棄這些變更?", unsavedChangesCloseCancel: "繼續編輯",