Found by a six-lens audit of the desktop app on 27 Aug 2026. Every finding was reproduced against the code, not inferred.
Severity: low · audit rank 15 of 16.
Breaks: every branch is a bare await fetch(...), and b.onclick = () => runAction(def) (:1856) discards the promise. requireEngine is consulted only when the panel opens. Worst on Export (export_chats), the app's only backup path: the fetch rejects, navigator.clipboard.writeText is never reached, no toast appears, and the user pastes whatever was already on the clipboard into a file believing it is a backup.
Who / likelihood: any engine restart or crash with the Settings panel open — and the settings that restart the engine are in that same panel.
Where: ui/index.html:1870-1938. open_data_dir is the only branch that degrades correctly.
--- a/ui/index.html
+++ b/ui/index.html
@@ -1870,3 +1870,11 @@
async function runAction(def){
if(def.confirm && !await askConfirm(def.confirm.message,{ok:'Continue'})) return;
+ try{ await runActionInner(def); }
+ catch(e){ toast('That did not run: '+(e && e.message ? e.message : e)); }
+}
+// A failed export that says nothing is worse than no export: the user pastes
+// a stale clipboard into a file and believes they have a backup.
+async function runActionInner(def){
if(def.action==='export'){
(the rest of the body unchanged, moved into runActionInner; the export branch additionally needs if(!r.ok) throw new Error(...) on each fetch, since a 500 does not reject).
Test — frontend/tests/engine-down.test.mjs (that file's exact subject): with the engine stub returning a network error, click Export and assert (a) navigator.clipboard.writeText was not called with stale content and (b) an error is visible. Today the click resolves into silence.
Not yet fixed. Filed so it is not lost with the session that found it. The fix and the test above are proposals from the audit — worth re-checking against current main before implementing, since the file has moved since.
Breaks: every branch is a bare
await fetch(...), andb.onclick = () => runAction(def)(:1856) discards the promise.requireEngineis consulted only when the panel opens. Worst on Export (export_chats), the app's only backup path: the fetch rejects,navigator.clipboard.writeTextis never reached, no toast appears, and the user pastes whatever was already on the clipboard into a file believing it is a backup.Who / likelihood: any engine restart or crash with the Settings panel open — and the settings that restart the engine are in that same panel.
Where:
ui/index.html:1870-1938.open_data_diris the only branch that degrades correctly.(the rest of the body unchanged, moved into
runActionInner; the export branch additionally needsif(!r.ok) throw new Error(...)on each fetch, since a 500 does not reject).Test —
frontend/tests/engine-down.test.mjs(that file's exact subject): with the engine stub returning a network error, click Export and assert (a)navigator.clipboard.writeTextwas not called with stale content and (b) an error is visible. Today the click resolves into silence.Not yet fixed. Filed so it is not lost with the session that found it. The fix and the test above are proposals from the audit — worth re-checking against current
mainbefore implementing, since the file has moved since.