fix(sweep): keep the UI sweep away from real wallet data

The full UI sweep (dev builds only) ran its demo surfaces against the
real profile. Its teardown deleted wallet-{cold,trading,mining,donations,
2023,payroll}.dat from the real datadir, it created zero-filled wallet
files and picker-demo/extra-wallets folders, its demo wallet-index upsert
re-minted the active wallet's scopeId (hiding "w:"-scoped contacts), the
Contacts tab could re-attach legacy-scoped contacts to such a throwaway
id in addressbook.json, and it persisted the demo active wallet file and
Mining pool worker.

- Preflight: a full sweep is refused (toast + log line) while a real
  workflow is in flight: a pending seed_migration_* setting or open
  migrate dialog, a wallet switch, an encryption/import workflow, an
  encrypt/PIN/lock operation, the seed-backup dialog, or a bootstrap.
  The DRAGONX_FULL_SWEEP hook then exits as for an empty catalog.
- Persistence latches: AddressBook (snapshot on the first sweepBegin,
  save/load latched while a demo-contacts surface is shown), WalletIndex
  and Settings (active wallet file, contacts view mode, pool URL/worker)
  for the whole sweep. Settings::sweepEnd() is the last statement of
  clearDemoWalletData and flushes once only if a save was suppressed; a
  quit mid-sweep writes nothing to the profile (the 0700 sandbox, with
  zero-filled demo files only, may stay in the temp dir).
- Scope ids: during a full sweep activeWalletScopeId() returns the
  persisted id or "" and never mints one, so the Contacts tab cannot
  move legacy-scoped contacts to a throwaway id. The only address-book
  write a sweep can still cause is the one-time legacy re-attach to the
  wallet's persisted id, which opening Contacts does anyway.
- Sandbox: demo wallet files and folders go to a fresh random 0700 dir
  under the temp dir (exclusive create, a pre-existing path or symlink
  is refused), removed at sweep end. The teardown that deleted those six
  wallet files is gone.
- Cleanup: the migrate-to-seed state, wizard phase, demo chat
  conversations and a fabricated demo chat identity are reset at sweep
  end; the seed-backup surface always shows the demo phrase and a real
  z_exportmnemonic result is dropped while the sweep runs.
- i18n: one new key (screenshot_sweep_refused) in i18n.cpp and the 8
  language files.

Tests: the address book snapshots only on the first sweepBegin, keeps
addressbook.json byte-identical while latched and restores the real
book; the wallet index keeps wallets.json and the real scope id, also
with the background writer attached; Settings restores the latched
values and flushes once only when a save was suppressed; the sandbox
helper refuses a pre-existing path or symlink and removes only a
prefixed dir, never a symlink's target.

Release note: v2.0.x UI sweeps could delete wallet-{cold,trading,mining,
donations,2023,payroll}.dat from the data directory and re-scope
contacts; this cannot be undone automatically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:22:00 -05:00
co-authored by Claude Opus 5.5
parent 3ac46abe1a
commit 62a5577491
22 changed files with 625 additions and 40 deletions
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "Screenshot-Durchlauf ausführen",
"screenshot_sweep_desc": "Durchläuft jedes Design über jeden Tab und speichert von jedem einen Screenshot in tab-spezifischen Unterordnern im Screenshots-Ordner des Konfigurationsverzeichnisses (überschreibt den vorherigen Durchlauf). Läuft einige Sekunden.",
"screenshot_sweep_full": "Vollständiger UI-Durchlauf",
"screenshot_sweep_refused": "Vollständiger UI-Durchlauf abgelehnt; siehe Debug-Log",
"search_icons": "Symbole suchen...",
"search_placeholder": "Suchen...",
"sec_changing_passphrase": "Passphrase wird geändert...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "Ejecutar barrido de capturas",
"screenshot_sweep_desc": "Recorre cada tema en cada pestaña y guarda una captura de pantalla de cada una en subcarpetas por pestaña dentro de la carpeta de capturas del directorio de configuración (sobrescribiendo el barrido anterior). Se ejecuta durante unos segundos.",
"screenshot_sweep_full": "Barrido completo de la interfaz",
"screenshot_sweep_refused": "Barrido completo de la interfaz rechazado; ver registro de depuración",
"search_icons": "Buscar iconos...",
"search_placeholder": "Buscar...",
"sec_changing_passphrase": "Cambiando la frase de contraseña...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "Lancer la capture d'écran",
"screenshot_sweep_desc": "Parcourt chaque thème sur chaque onglet et enregistre une capture d'écran de chacun dans des sous-dossiers par onglet, sous le dossier screenshots du répertoire de configuration (en écrasant le balayage précédent). Dure quelques secondes.",
"screenshot_sweep_full": "Balayage complet de l'interface",
"screenshot_sweep_refused": "Balayage complet de l'interface refusé ; voir le journal de débogage",
"search_icons": "Rechercher des icônes...",
"search_placeholder": "Rechercher...",
"sec_changing_passphrase": "Modification de la phrase secrète...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "スクリーンショットスイープを実行",
"screenshot_sweep_desc": "すべてのテーマをすべてのタブで巡回し、それぞれのスクリーンショットを設定ディレクトリの screenshots フォルダ内のタブごとのサブフォルダに保存します(前回のスイープを上書きします)。数秒間実行されます。",
"screenshot_sweep_full": "UI全体スイープ",
"screenshot_sweep_refused": "UI全体スイープは拒否されました。デバッグログを確認してください",
"search_icons": "アイコンを検索...",
"search_placeholder": "検索...",
"sec_changing_passphrase": "パスフレーズを変更しています...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "스크린샷 스윕 실행",
"screenshot_sweep_desc": "모든 탭에 대해 모든 테마를 순회하며 각각의 스크린샷을 설정 디렉터리의 screenshots 폴더 아래 탭별 하위 폴더에 저장합니다(이전 스윕을 덮어씀). 몇 초 동안 실행됩니다.",
"screenshot_sweep_full": "전체 UI 스윕",
"screenshot_sweep_refused": "전체 UI 스윕이 거부되었습니다. 디버그 로그를 확인하세요",
"search_icons": "아이콘 검색...",
"search_placeholder": "검색...",
"sec_changing_passphrase": "암호문 변경 중...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "Executar varredura de capturas de tela",
"screenshot_sweep_desc": "Percorre cada tema em cada aba e salva uma captura de tela de cada um em subpastas por aba dentro da pasta de capturas de tela do diretório de configuração (sobrescrevendo a varredura anterior). É executado por alguns segundos.",
"screenshot_sweep_full": "Varredura completa da interface",
"screenshot_sweep_refused": "Varredura completa da interface recusada; veja o log de depuração",
"search_icons": "Pesquisar ícones...",
"search_placeholder": "Pesquisar...",
"sec_changing_passphrase": "Alterando a frase-senha...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "Запустить прогон скриншотов",
"screenshot_sweep_desc": "Перебирает каждую тему по всем вкладкам и сохраняет скриншот каждой в подпапки по вкладкам внутри папки screenshots в каталоге конфигурации (перезаписывая предыдущий проход). Выполняется несколько секунд.",
"screenshot_sweep_full": "Полный обход интерфейса",
"screenshot_sweep_refused": "Полный обход интерфейса отклонён; см. журнал отладки",
"search_icons": "Поиск значков...",
"search_placeholder": "Поиск...",
"sec_changing_passphrase": "Смена парольной фразы...",
+1
View File
@@ -1666,6 +1666,7 @@
"screenshot_sweep": "运行截图批处理",
"screenshot_sweep_desc": "遍历每个标签页的每一种主题,并将每一个的截图保存到配置目录 screenshots 文件夹下的各标签页子文件夹中(覆盖上一次的遍历)。运行几秒钟。",
"screenshot_sweep_full": "完整 UI 遍历",
"screenshot_sweep_refused": "完整 UI 遍历被拒绝;请查看调试日志",
"search_icons": "搜索图标...",
"search_placeholder": "搜索...",
"sec_changing_passphrase": "正在更改密码短语…",