[1.44.0] — 2026-05-18
WS2 #4 + #9 — focus-trapped confirmation for destructive parent writes.
🐛 Fixes
fix(a11y/safety): UI.confirm() gate before whole-file parent overwrites— two UX-audit HIGHs, both data-loss: (#4)config.jssaveProfileRaw/saveModesRawreplaced the ENTIRE parentconfig/profile.yml/modes/_profile.mdwith no confirmation; (#9)tracker.jsNormalize/Dedup/Merge rewrote parentdata/applications.mdin place with no confirmation. AddedUI.confirm(title, message, opts)topublic/js/api.js— a focus-trapped dialog reusing the existing WAI-ARIA modal infra (focus-return, Tab-trap), returningPromise<boolean>. A new_onClosehook fires fromcloseModal()so EVERY dismissal path (Esc / backdrop / × / Cancel) resolvesfalse; only the explicit confirm button resolvestrue. Focus defaults to Cancel (safe choice for a destructive op). NOT nativeconfirm()(auto-dismissed in embeds, not focus-trapped). All three call sites are now gated before theAPI.put/API.post. Verified live: Normalize → focus-trapped "Переписать applications.md?" modal, focus on Cancel, Cancel dismisses with no POST, 0 console errors.
🌐 i18n
- 8 new keys × 8 locales —
common.confirm,config.rawConfirmTitle/Ok,config.profileRawConfirmBody,config.modesRawConfirmBody,track.fixConfirmTitle/Body/Ok. The{op}placeholder intrack.fixConfirmBodyis preserved verbatim across all locales (runtime-substituted).i18n-coveragegate green.
🧪 Tests
test: tests/confirm-gate.test.mjs— 8 cases:UI.confirmexported,_onCloseresolves false on every dismissal path, Cancel-default focus,modal()back-compat onClose arg, all 3 destructive sites gated before their write, no nativeconfirm()left, 8 i18n keys present with{op}intact. 644 → 652.
Verification
$ npm run test:ci
# 652 / 652 · ✓ no .also( leftovers · ✓ CHANGELOG parity: all 8 locales at v1.44.0
# Playwright: #/tracker Normalize → focus-trapped modal, focus=Cancel, Cancel→no POST, 0 errors