fix(settings): report apply failures without stalling

This commit is contained in:
jearhe authored and GitHub committed 2026-08-18 10:50:47 +08:00
1 parent 840082d73b
commit cddc6a2c40
10 files changed
+74 -3

No files matched your search

@@ -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<boolean> {
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() {
@@ -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<boolean>");
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"');
});
});
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -5598,6 +5598,7 @@ export default withEnglishFallback({
duckDbWorkerProcessIsolationRestartRequired: "この変更を有効にするには DBX を再起動してください。",
restartDbx: "DBX を再起動",
restartDbxFailed: "DBX の再起動に失敗しました: {error}",
applyFailed: "設定の適用に失敗しました: {error}",
unsavedChangesCloseTitle: "未保存の変更",
unsavedChangesCloseMessage: "未保存の設定変更があります。今すぐ閉じると失われます。これらの変更を破棄しますか?",
unsavedChangesCloseCancel: "編集を続ける",
+1
View File
@@ -5291,6 +5291,7 @@ export default withEnglishFallback({
duckDbWorkerProcessIsolationRestartRequired: "이 변경 사항을 적용하려면 DBX를 다시 시작하세요.",
restartDbx: "DBX 다시 시작",
restartDbxFailed: "DBX 다시 시작 실패: {error}",
applyFailed: "설정 적용 실패: {error}",
unsavedChangesCloseTitle: "저장하지 않은 변경 사항",
unsavedChangesCloseMessage: "저장하지 않은 설정 변경 사항이 있습니다. 지금 닫으면 사라집니다. 이 변경 사항을 취소하시겠습니까?",
unsavedChangesCloseCancel: "계속 편집",
+1
View File
@@ -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",
+1
View File
@@ -5808,6 +5808,7 @@ export default withEnglishFallback({
duckDbWorkerProcessIsolationRestartRequired: "重启 DBX 后生效。",
restartDbx: "重启 DBX",
restartDbxFailed: "重启 DBX 失败:{error}",
applyFailed: "应用设置失败:{error}",
unsavedChangesCloseTitle: "未保存的更改",
unsavedChangesCloseMessage: "当前设置有未保存的更改,关闭后将丢失。是否放弃这些更改?",
unsavedChangesCloseCancel: "继续编辑",
+1
View File
@@ -4899,6 +4899,7 @@ export default withEnglishFallback({
duckDbWorkerProcessIsolationRestartRequired: "重啟 DBX 後生效。",
restartDbx: "重啟 DBX",
restartDbxFailed: "重啟 DBX 失敗:{error}",
applyFailed: "套用設定失敗:{error}",
unsavedChangesCloseTitle: "未儲存的變更",
unsavedChangesCloseMessage: "目前設定有未儲存的變更,關閉後將遺失。是否放棄這些變更?",
unsavedChangesCloseCancel: "繼續編輯",