diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a6385e..7b7f00b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -678,3 +678,34 @@ change takes effect immediately (the method is already re-run from
**Outcome:** DONE (delivered together with fix #10 in a single ZIP for user
testing before commit).
+
+## 12. Translated the refactoring docs to English and renamed two of them
+
+**Request:** per rule R1 (all output, including documentation, must be in
+English), translate the remaining Italian documentation files. The user also
+asked to translate the file *names* of two of them.
+
+**What changed:**
+- `CHANGELOG_REFACTOR.md`
+ - Translated sections 1-16 (the whole report header plus §1-§16) from Italian
+ to English. Sections 17-18 were already in English and were left untouched.
+ - All technical content was preserved verbatim: code blocks, commit hashes,
+ tables, file/class/method names, version numbers and structure.
+ - Updated the §12 history line to mention the new name of the diagnosis file
+ (`GUI_DIAGNOSIS.md`, originally `DIAGNOSI_GUI.md`).
+- `DIAGNOSI_GUI.md` → renamed to `GUI_DIAGNOSIS.md` (via `git mv`) and fully
+ translated to English, **title included**. All code blocks, line references,
+ severity emojis, tables and structure were preserved.
+- `REPORT_NETWORKING_PARALLELO.md` → renamed to `PARALLEL_NETWORKING_REPORT.md`
+ (via `git mv`). Its content was already in English, so only the file name was
+ translated.
+
+**Verification:**
+- Scanned the translated regions for leftover Italian words: none found (only
+ English false-positives from the regex).
+- Confirmed sections 17-18 of `CHANGELOG_REFACTOR.md` are unchanged
+ (the diff hunks stop before §17).
+- Confirmed no other file in the repo still references the old file names.
+
+**Outcome:** DONE (documentation-only change; no plugin code touched, so the
+zip-first step does not apply).
diff --git a/CHANGELOG_REFACTOR.md b/CHANGELOG_REFACTOR.md
index 4d7484d..de6714c 100644
--- a/CHANGELOG_REFACTOR.md
+++ b/CHANGELOG_REFACTOR.md
@@ -1,66 +1,66 @@
-# BAL — Resoconto del refactoring (per l'autore originale)
+# BAL — Refactoring report (for the original author)
-Questo documento elenca **tutte** le modifiche apportate al plugin BAL
-(Bitcoin After Life) rispetto alla versione originale `0.2.8`.
+This document lists **all** the changes made to the BAL plugin
+(Bitcoin After Life) with respect to the original version `0.2.8`.
-**Principio guida:** refactoring **conservativo e a comportamento invariato**
-(Approccio A). La logica di business è stata mantenuta **byte-identica** dove
-possibile; sono cambiati soprattutto la **disposizione dei file** e gli
-**import**. Nessuna riscrittura algoritmica.
+**Guiding principle:** **conservative, behaviour-preserving** refactoring
+(Approach A). The business logic was kept **byte-identical** where possible;
+what changed are mainly the **file layout** and the **imports**. No algorithmic
+rewrite.
-Ambiente di verifica: **Electrum 4.7.2** + **PyQt6** (l'ultima release stabile
-che espone `json_db.register_dict`).
+Verification environment: **Electrum 4.7.2** + **PyQt6** (the latest stable
+release that exposes `json_db.register_dict`).
---
-## 1. Riorganizzazione della struttura (separazione logica / GUI)
+## 1. Structure reorganization (logic / GUI separation)
-Il problema principale segnalato era che la logica e la grafica erano
-mescolate, in particolare in un unico file `qt.py` da **4131 righe**.
+The main reported problem was that logic and graphics were mixed together, in
+particular in a single `qt.py` file of **4131 lines**.
-### Struttura PRIMA (flat, 7 file)
+### Structure BEFORE (flat, 7 files)
```
BAL/
-├── __init__.py (vuoto, 0 righe)
-├── bal.py (243) logica + plugin base
-├── util.py (533) helper
-├── heirs.py (791) modello eredi + costruzione tx
-├── will.py (927) modello will/WillItem
-├── willexecutors.py (374) networking will-executor
-├── qt.py (4131) TUTTA la GUI + il Plugin in un solo file
+├── __init__.py (empty, 0 lines)
+├── bal.py (243) logic + plugin base
+├── util.py (533) helpers
+├── heirs.py (791) heirs model + tx building
+├── will.py (927) will/WillItem model
+├── willexecutors.py (374) will-executor networking
+├── qt.py (4131) ALL the GUI + the Plugin in a single file
└── bal_resources.py (14)
```
-### Struttura DOPO (core/ vs gui/)
+### Structure AFTER (core/ vs gui/)
```
bal/
-├── manifest.json metadati conformi allo standard
-├── qt.py shim di caricamento (re-export di Plugin)
-├── __init__.py docstring di architettura + __version__
-├── core/ LOGICA senza dipendenze Qt
-│ ├── util.py (ex util.py)
-│ ├── plugin_base.py (ex bal.py)
-│ ├── heirs.py (ex heirs.py)
-│ ├── will.py (ex will.py)
-│ └── willexecutors.py (ex willexecutors.py)
-└── gui/qt/ PRESENTAZIONE PyQt6
- ├── theme.py (59) mappatura stato → colore
- ├── common.py (155) import condivisi + helper GUI
- ├── widgets.py (782) widget "foglia"
+├── manifest.json standard-compliant metadata
+├── qt.py loading shim (re-export of Plugin)
+├── __init__.py architecture docstring + __version__
+├── core/ LOGIC with no Qt dependencies
+│ ├── util.py (was util.py)
+│ ├── plugin_base.py (was bal.py)
+│ ├── heirs.py (was heirs.py)
+│ ├── will.py (was will.py)
+│ └── willexecutors.py (was willexecutors.py)
+└── gui/qt/ PyQt6 PRESENTATION
+ ├── theme.py (59) status → colour mapping
+ ├── common.py (155) shared imports + GUI helpers
+ ├── widgets.py (782) "leaf" widgets
├── calendar.py (80) BalCalendar
- ├── dialogs.py (1127) finestre di dialogo
- ├── lists.py (957) viste ad albero (eredi/preview/executor)
- ├── window.py (952) controller GUI per-wallet (BalWindow)
- └── plugin.py (273) classe Plugin (@hook Electrum → GUI)
+ ├── dialogs.py (1127) dialog windows
+ ├── lists.py (957) tree views (heirs/preview/executor)
+ ├── window.py (952) per-wallet GUI controller (BalWindow)
+ └── plugin.py (273) Plugin class (@hook Electrum → GUI)
```
-Il file `qt.py` da 4131 righe è stato suddiviso per **responsabilità**. I
-**corpi delle classi sono stati copiati verbatim** (riga per riga) per non
-toccare la logica delicata delle transazioni di eredità.
+The 4131-line `qt.py` file was split by **responsibility**. The **class bodies
+were copied verbatim** (line by line) so as not to touch the delicate
+inheritance-transaction logic.
-### Mappa: dove sono finite le 40 classi/funzioni di `qt.py`
+### Map: where the 40 classes/functions of `qt.py` ended up
-| Classe/funzione (riga orig.) | Nuovo modulo |
+| Class/function (orig. line) | New module |
|-------------------------------------|-------------------------|
| `Plugin` (67) | `gui/qt/plugin.py` |
| `shown_cv` (317) | `gui/qt/common.py` |
@@ -104,257 +104,255 @@ toccare la logica delicata delle transazioni di eredità.
---
-## 2. Rimozioni (codice morto / debug) — comportamento invariato
+## 2. Removals (dead / debug code) — behaviour unchanged
-Tutte le rimozioni seguenti sono state verificate come **non utilizzate** o
-**puramente di debug**, quindi non alterano il comportamento del plugin.
+All the following removals were verified as **unused** or **purely debug**, so
+they do not alter the plugin's behaviour.
-1. **`util.py` → `core/util.py`**: rimossi tre helper di debug usati solo per
- stampe a console:
- - `print_var()` (orig. riga 439)
- - `print_utxo()` (orig. riga 474)
- - `print_prevout()` (orig. riga 486)
+1. **`util.py` → `core/util.py`**: removed three debug helpers used only for
+ console printing:
+ - `print_var()` (orig. line 439)
+ - `print_utxo()` (orig. line 474)
+ - `print_prevout()` (orig. line 486)
-2. **`bal.py` → `core/plugin_base.py`**: rimossa la funzione **stub vuota**
- `get_will_settings(x)` (orig. righe 12-14):
+2. **`bal.py` → `core/plugin_base.py`**: removed the **empty stub** function
+ `get_will_settings(x)` (orig. lines 12-14):
```python
def get_will_settings(x):
# print(x)
pass
```
- ⚠️ Verificato: **non era riferita da nessun `register_dict`** — i tre
- `register_dict` usano `tuple`, `dict`, `lambda x: x`. Quindi era codice
- morto. La funzione **usata** `get_will(x)` è stata mantenuta identica.
+ ⚠️ Verified: **it was not referenced by any `register_dict`** — the three
+ `register_dict` calls use `tuple`, `dict`, `lambda x: x`. So it was dead
+ code. The **used** function `get_will(x)` was kept identical.
-3. **`will.py` (`WillItem`) → spostato in `gui/qt/theme.py`**: il metodo
- `WillItem.get_color()` (orig. riga 852) restituiva colori esadecimali —
- è logica di **presentazione**, non di dominio. È stato spostato fuori dal
- modello e trasformato nella funzione `status_color(will_item)` in
- `gui/qt/theme.py`. **Verificato byte-identico** su tutte le combinazioni di
- stato (stessa catena di `get_status(...)`, stessi codici colore).
+3. **`will.py` (`WillItem`) → moved to `gui/qt/theme.py`**: the method
+ `WillItem.get_color()` (orig. line 852) returned hexadecimal colours —
+ it is **presentation** logic, not domain logic. It was moved out of the
+ model and turned into the function `status_color(will_item)` in
+ `gui/qt/theme.py`. **Verified byte-identical** across all status
+ combinations (same `get_status(...)` chain, same colour codes).
---
-## 3. Cambi di import (necessari per la nuova struttura)
+## 3. Import changes (required by the new structure)
-Gli import sono stati aggiornati da "flat" a "a package". Esempi:
+The imports were updated from "flat" to "package" style. Examples:
-| Prima | Dopo |
+| Before | After |
|------------------------------------|---------------------------------------|
| `from .bal import BalPlugin` | `from .plugin_base import BalPlugin` (in willexecutors) |
-| `from .util import Util` | `from .util import Util` (invariato, ora dentro core/) |
-| (in qt.py) `from .bal import ...` | i moduli GUI importano da `...core.X` |
+| `from .util import Util` | `from .util import Util` (unchanged, now inside core/) |
+| (in qt.py) `from .bal import ...` | the GUI modules import from `...core.X` |
-- Aggiunti `from .common import _, _logger` nei moduli GUI, perché `import *`
- **non** esporta i nomi che iniziano con underscore.
-- Aggiunti 3 import "lazy" (dentro le funzioni) in `dialogs.py` per spezzare il
- ciclo `dialogs ↔ lists` (lists importa `BalBuildWillDialog` da dialogs).
+- Added `from .common import _, _logger` in the GUI modules, because `import *`
+ does **not** export names starting with an underscore.
+- Added 3 "lazy" imports (inside the functions) in `dialogs.py` to break the
+ `dialogs ↔ lists` cycle (lists imports `BalBuildWillDialog` from dialogs).
-La **logica interna dei metodi** non è stata toccata: `prepare_transactions()`,
-`buildTransactions()` ecc. sono verbatim.
+The **internal logic of the methods** was not touched: `prepare_transactions()`,
+`buildTransactions()`, etc. are verbatim.
---
-## 4. Packaging conforme allo standard Electrum
+## 4. Electrum-standard packaging
-`manifest.json` reso conforme a https://plugins.electrum.org/developers.html :
+`manifest.json` made compliant with https://plugins.electrum.org/developers.html :
-| Campo | Prima | Dopo |
+| Field | Before | After |
|------------------|--------------------|-------------------------------|
-| `name` | `"BAL"` | `"bal"` (minuscolo = nome dir) |
-| `version` | (assente, era solo nella description) | `"0.2.8"` |
-| `description` | con `
` HTML | testo pulito |
-| `licence` | (assente) | `"MIT"` |
-| `fullname`/`author`/`available_for`/`icon` | presenti | invariati |
+| `name` | `"BAL"` | `"bal"` (lowercase = dir name) |
+| `version` | (absent, was only in the description) | `"0.2.8"` |
+| `description` | with HTML `
` | clean text |
+| `licence` | (absent) | `"MIT"` |
+| `fullname`/`author`/`available_for`/`icon` | present | unchanged |
-- `__init__.py` (era **vuoto**): ora contiene la docstring di architettura e
+- `__init__.py` (was **empty**): now contains the architecture docstring and
`__version__ = "0.2.8"`.
-- Portati nel package: `LICENSE`, `VERSION`, `README.md`, `bal_resources.py`,
- e la cartella `wallet_util/` (invariata).
+- Brought into the package: `LICENSE`, `VERSION`, `README.md`, `bal_resources.py`,
+ and the `wallet_util/` folder (unchanged).
---
-## 5. CORREZIONE BUG: caricamento come plugin esterno (.zip)
+## 5. BUG FIX: loading as an external plugin (.zip)
-Durante i test su **Electrum 4.7.2 portable per Windows** sono emersi due
-problemi reali nel caricare il plugin come **plugin esterno da .zip**:
+During testing on **Electrum 4.7.2 portable for Windows**, two real problems
+emerged when loading the plugin as an **external plugin from .zip**:
### Bug 5a — `ModuleNotFoundError: No module named 'electrum_external_plugins'`
-- **Causa:** Electrum carica i plugin esterni da zip sotto il package sintetico
- `electrum_external_plugins.bal`, ed esegue **solo** l'`__init__` del package e
- il modulo `qt`. Non registra il package radice sintetico né i sotto-package
- annidati (`gui`, `gui.qt`). Un semplice `from .gui.qt.plugin import Plugin`
- fallisce risalendo ai parent mancanti.
-- **Fix:** `qt.py` ora è uno shim resiliente che (1) rileva a runtime il proprio
- nome di package (`__package__`), (2) ricostruisce in `sys.modules` gli
- eventuali package padre mancanti, (3) importa `Plugin` con
- `importlib.import_module`. Funziona **sia** come plugin interno
- (`electrum.plugins.bal`) **sia** esterno (`electrum_external_plugins.bal`).
+- **Cause:** Electrum loads external plugins from zip under the synthetic
+ package `electrum_external_plugins.bal`, and runs **only** the package
+ `__init__` and the `qt` module. It does not register the synthetic root
+ package nor the nested sub-packages (`gui`, `gui.qt`). A simple
+ `from .gui.qt.plugin import Plugin` fails when walking up to the missing
+ parents.
+- **Fix:** `qt.py` is now a resilient shim that (1) detects its own package
+ name at runtime (`__package__`), (2) rebuilds in `sys.modules` any missing
+ parent packages, (3) imports `Plugin` with `importlib.import_module`. It
+ works **both** as an internal plugin (`electrum.plugins.bal`) **and** as an
+ external one (`electrum_external_plugins.bal`).
### Bug 5b — `zlib.error: Error -5 ... incomplete or truncated stream`
-- **Causa:** alcune build portable di Electrum su Windows non riescono a
- decomprimere con `zipimport` archivi che contengono **voci di directory** o
- compressione non standard.
-- **Fix:** aggiunto `build_zip.py`, che genera un archivio "zipimport-friendly":
- solo file (nessuna voce di directory), DEFLATE standard, ordine deterministico
- (SHA-256 riproducibile), escludendo `__pycache__`/`*.pyc`. Stampa anche
- l'hash SHA-256 per verificare l'integrità del download.
+- **Cause:** some Electrum portable builds on Windows fail to decompress with
+ `zipimport` archives that contain **directory entries** or non-standard
+ compression.
+- **Fix:** added `build_zip.py`, which generates a "zipimport-friendly"
+ archive: files only (no directory entries), standard DEFLATE, deterministic
+ ordering (reproducible SHA-256), excluding `__pycache__`/`*.pyc`. It also
+ prints the SHA-256 hash to verify the integrity of the download.
---
-## 6. Test aggiunti
+## 6. Tests added
-- `tests/smoke_test.py` — verifica import + comportamento di base
- (`BalTimestamp`, helper di `Util`, costanti `HEIR_*`, stati di `WillItem`,
- hook del `Plugin`).
-- `tests/external_zip_test.py` — riproduce **fedelmente** la sequenza di
- caricamento di un plugin esterno da zip di Electrum (regressione per il
- Bug 5a/5b).
+- `tests/smoke_test.py` — checks imports + basic behaviour
+ (`BalTimestamp`, `Util` helpers, `HEIR_*` constants, `WillItem` states,
+ `Plugin` hooks).
+- `tests/external_zip_test.py` — **faithfully** reproduces Electrum's loading
+ sequence for an external plugin from zip (regression test for Bug 5a/5b).
-Tutti i test passano sotto Electrum 4.7.2 + PyQt6.
+All tests pass under Electrum 4.7.2 + PyQt6.
---
-## 7. Riepilogo: cosa NON è cambiato
+## 7. Summary: what did NOT change
-- La logica di costruzione delle transazioni (`heirs.py`, `will.py`).
-- I valori e i tipi di `json_db.register_dict(...)`.
-- I codici colore degli stati (solo spostati in `theme.py`).
-- L'algoritmo di tutte le classi GUI (copiate verbatim).
-- Il formato dei dati salvati nel wallet.
+- The transaction-building logic (`heirs.py`, `will.py`).
+- The values and types of `json_db.register_dict(...)`.
+- The status colour codes (only moved to `theme.py`).
+- The algorithm of all the GUI classes (copied verbatim).
+- The format of the data saved in the wallet.
-## 8. Note / raccomandazioni
+## 8. Notes / recommendations
-- Il plugin **richiede Electrum 4.7.2**: `json_db.register_dict` è stato
- **rimosso** nelle versioni successive (master), dove andrebbe sostituito con
- `stored_dict.register_name`. Valutare un adeguamento se si vuole supportare
- Electrum più recente.
-- Prima del rilascio è consigliata una prova **end-to-end in una sessione
- Electrum reale** (preferibilmente su testnet), oltre agli smoke test.
+- The plugin **requires Electrum 4.7.2**: `json_db.register_dict` was
+ **removed** in later versions (master), where it should be replaced with
+ `stored_dict.register_name`. Consider an update if you want to support a
+ more recent Electrum.
+- Before release, an **end-to-end test in a real Electrum session** is
+ recommended (preferably on testnet), in addition to the smoke tests.
---
-## 9. CORREZIONI GUI — finestre e ciclo di vita (B1-B10)
+## 9. GUI FIXES — windows and lifecycle (B1-B10)
-Dopo il refactoring di struttura sono stati corretti **dieci difetti grafici e
-di ciclo di vita** delle finestre, già presenti nel codice originale. La logica
-di business è rimasta **byte-identica** (nessuna modifica a `bal/core/*`): sono
-cambiati solo **presentazione, parent, modalità, z-order, ciclo di vita e
-cleanup** delle finestre Qt.
+After the structural refactoring, **ten graphical and lifecycle defects** of
+the windows were fixed, all already present in the original code. The business
+logic remained **byte-identical** (no changes to `bal/core/*`): only the
+**presentation, parent, modality, z-order, lifecycle and cleanup** of the Qt
+windows changed.
-Sintomi segnalati dall'utente, ora risolti:
-- **(S1)** le finestre del plugin sparivano dietro la finestra di Electrum;
-- **(S2)** alcuni meccanismi funzionavano solo dopo aver chiuso e riavviato
- Electrum.
+Symptoms reported by the user, now resolved:
+- **(S1)** the plugin windows disappeared behind the Electrum window;
+- **(S2)** some mechanisms worked only after closing and restarting Electrum.
-| ID | Problema (presente nell'originale) | Correzione applicata |
+| ID | Problem (present in the original) | Applied fix |
|-----|----------------------------------------------------------------------|----------------------|
-| B1 | `self.parent = parent` sovrascriveva il metodo `parent()` di Qt, rompendo la gerarchia delle finestre | rinominato in `self._bal_parent` (in `dialogs.py`, `lists.py`, `widgets.py`); il parent reale passa da `top_level_of(parent)` |
-| B2 | dialoghi aperti con `.show()` non modale → finivano sotto la finestra principale | sostituiti con `show_on_top()` / `show_modal()` e parent corretto |
-| B3 | messaggio "Please restart Electrum to activate the BAL plugin": il plugin si attivava solo dopo riavvio | inizializzazione **a caldo** con `_setup_window()` che replica `load_wallet` — niente più riavvio |
-| B4 | chiave del dizionario finestre usava il **metodo** `winId` invece del valore | chiave stabile `_window_key()` basata su `id(window)` |
-| B5 | `on_close` ingoiava tutti gli errori con `except: pass` | riscritto: niente `except:pass`, log per ogni passo, reset pulito dello stato |
-| B6 | `BalBlockingWaitingDialog` bloccava il thread della GUI (`processEvents` commentato) | ripristinato `processEvents()` → GUI reattiva durante l'attesa |
-| B7 | `closeEvent`/`hideEvent` con cleanup del thread commentato | gestione esplicita di `closeEvent`/`hideEvent` + chiamata a `super()` |
-| B8 | `closeEvent` incompleto in alcuni dialog | gestione uniforme dello stato di chiusura |
-| B9 | `show()+raise_()` senza `activateWindow()` né modalità → finestra non in primo piano | `bring_to_front()` = `raise_()` + `activateWindow()` |
-| B10 | gestione multi-wallet / multi-finestra fragile; menu cercato per titolo `&Tools` | uso dell'API ufficiale `window.tools_menu` |
+| B1 | `self.parent = parent` overrode Qt's `parent()` method, breaking the window hierarchy | renamed to `self._bal_parent` (in `dialogs.py`, `lists.py`, `widgets.py`); the real parent comes from `top_level_of(parent)` |
+| B2 | dialogs opened with non-modal `.show()` → ended up under the main window | replaced with `show_on_top()` / `show_modal()` and the correct parent |
+| B3 | message "Please restart Electrum to activate the BAL plugin": the plugin activated only after a restart | **hot** initialization with `_setup_window()` that replicates `load_wallet` — no more restart |
+| B4 | the windows-dictionary key used the `winId` **method** instead of its value | stable `_window_key()` key based on `id(window)` |
+| B5 | `on_close` swallowed all errors with `except: pass` | rewritten: no `except:pass`, logging at every step, clean state reset |
+| B6 | `BalBlockingWaitingDialog` blocked the GUI thread (`processEvents` commented out) | restored `processEvents()` → responsive GUI during the wait |
+| B7 | `closeEvent`/`hideEvent` with the thread cleanup commented out | explicit handling of `closeEvent`/`hideEvent` + call to `super()` |
+| B8 | incomplete `closeEvent` in some dialogs | uniform handling of the closing state |
+| B9 | `show()+raise_()` without `activateWindow()` nor modality → window not in the foreground | `bring_to_front()` = `raise_()` + `activateWindow()` |
+| B10 | fragile multi-wallet / multi-window handling; menu looked up by the `&Tools` title | use of the official `window.tools_menu` API |
-### Nuovo modulo: `gui/qt/window_utils.py` (119 righe)
+### New module: `gui/qt/window_utils.py` (119 lines)
-Gli helper per la gestione delle finestre sono stati **centralizzati** in un
-unico modulo, così la stessa logica non viene duplicata nei vari dialog:
+The window-management helpers were **centralized** in a single module, so that
+the same logic is not duplicated across the various dialogs:
-- `top_level_of(widget)` — risale alla finestra di primo livello corretta da
- usare come parent;
-- `bring_to_front(window)` — `raise_()` + `activateWindow()` per portare in
- primo piano;
-- `stop_thread(thread)` — stop+wait sicuro di un `TaskThread`;
-- `show_modal(dialog)` — apertura modale corretta (`exec()`);
-- `show_on_top(window)` — apertura non modale ma sopra le altre finestre.
+- `top_level_of(widget)` — walks up to the correct top-level window to use as
+ parent;
+- `bring_to_front(window)` — `raise_()` + `activateWindow()` to bring to the
+ foreground;
+- `stop_thread(thread)` — safe stop+wait of a `TaskThread`;
+- `show_modal(dialog)` — correct modal opening (`exec()`);
+- `show_on_top(window)` — non-modal opening but above the other windows.
-`gui/qt/common.py` importa questi helper e li rende disponibili al resto della
-GUI.
+`gui/qt/common.py` imports these helpers and makes them available to the rest
+of the GUI.
---
-## 10. CORREZIONE BUG: download lista will-executor
+## 10. BUG FIX: will-executor list download
-Dopo l'installazione del pacchetto con le correzioni GUI, l'utente ha
-segnalato che il comando **"download list"** dei will-executor non scaricava
-più la lista.
+After installing the package with the GUI fixes, the user reported that the
+will-executor **"download list"** command no longer downloaded the list.
-### Indagine
+### Investigation
-Il codice di rete (`core/willexecutors.py`: `send_request`, `handle_response`,
-`download_list`, `initialize_willexecutor`) è stato confrontato riga per riga
-con l'originale Gitea ed è risultato **byte-identico** (l'unica differenza è il
-parametro aggiuntivo `welist_server` in `download_list`, retro-compatibile).
+The network code (`core/willexecutors.py`: `send_request`, `handle_response`,
+`download_list`, `initialize_willexecutor`) was compared line by line with the
+original Gitea version and turned out to be **byte-identical** (the only
+difference is the extra `welist_server` parameter in `download_list`, which is
+backward-compatible).
-Durante l'indagine sono comunque emersi e stati corretti **due difetti reali**
-introdotti dalle correzioni GUI, che potevano "perdere" il risultato del
-download:
+During the investigation, **two real defects** introduced by the GUI fixes were
+nevertheless found and corrected, which could "lose" the download result:
-1. **`BalDialog.closeEvent`/`hideEvent` fermavano il `TaskThread`.** In Electrum
- `TaskThread.on_done` esegue `cb_done` (cioè `self.accept`, che **chiude** il
- dialog) **prima** di `cb_result` (cioè `on_success`, che **aggiorna** la
- lista). Fermare il thread alla chiusura del dialog **scartava** quindi il
- risultato appena scaricato. → I due metodi sono stati riportati a **non**
- fermare il thread (con commento esplicativo nel codice).
-2. **`BalWaitingDialog.exe()` usava una modalità sbagliata** (`show_modal` /
- `WindowModal`). → Ripristinato l'originale `self.exec()`, aggiungendo prima
- `bring_to_front(self)` per garantire il primo piano.
+1. **`BalDialog.closeEvent`/`hideEvent` stopped the `TaskThread`.** In Electrum,
+ `TaskThread.on_done` runs `cb_done` (i.e. `self.accept`, which **closes** the
+ dialog) **before** `cb_result` (i.e. `on_success`, which **updates** the
+ list). Stopping the thread when the dialog closed therefore **discarded** the
+ result that had just been downloaded. → The two methods were restored so they
+ do **not** stop the thread (with an explanatory comment in the code).
+2. **`BalWaitingDialog.exe()` used the wrong modality** (`show_modal` /
+ `WindowModal`). → Restored the original `self.exec()`, first adding
+ `bring_to_front(self)` to guarantee the foreground.
-Inoltre i percorsi del **pulsante** e del **wizard** (che prima scaricavano in
-modi diversi e con messaggi diversi) sono stati **unificati** in un unico
-helper `fetch_will_executors_list`, eseguito dentro il worker del `TaskThread`.
+In addition, the **button** and **wizard** paths (which previously downloaded
+in different ways and with different messages) were **unified** into a single
+helper `fetch_will_executors_list`, run inside the `TaskThread` worker.
-### Causa vera del mancato download: ambientale, NON del plugin
+### Real cause of the failed download: environmental, NOT the plugin
-Una probe di controllo con `urllib` che **bypassava completamente Electrum**
-falliva ugualmente con `WinError 10054` ("connection forcibly closed by remote
-host"): segno che la **rete/ISP dell'utente resettava la connessione HTTPS**
-verso `welist.bitcoin-after.life`. La conferma definitiva: **attivando una VPN
-il download è andato a buon fine.**
+A control probe with `urllib` that **completely bypassed Electrum** also failed
+with `WinError 10054` ("connection forcibly closed by remote host"): a sign
+that the **user's network/ISP was resetting the HTTPS connection** to
+`welist.bitcoin-after.life`. The definitive confirmation: **with a VPN enabled
+the download succeeded.**
-L'originale "sembrava" funzionare perché spedisce comunque un will-executor di
-**default già incorporato** (`https://we.bitcoin-after.life`), quindi la lista
-non risultava mai del tutto vuota anche senza un download riuscito.
+The original "seemed" to work because it ships anyway a **built-in default**
+will-executor (`https://we.bitcoin-after.life`), so the list never appeared
+completely empty even without a successful download.
-### Pulizia finale (scelta dall'utente — "Opzione 1")
+### Final cleanup (chosen by the user — "Option 1")
-- **Finestra di attesa non bloccante** mantenuta (`BalWaitingDialog`), così la
- GUI non si congela durante il download.
-- **Fallback dell'URL**: prima l'URL configurato (`WELIST_SERVER`), poi quello
+- **Non-blocking waiting window** kept (`BalWaitingDialog`), so the GUI does
+ not freeze during the download.
+- **URL fallback**: first the configured URL (`WELIST_SERVER`), then the
hardcoded `https://welist.bitcoin-after.life/`.
-- **Diagnostica dettagliata spostata nei soli log** (rimossa la probe `urllib`
- dall'interfaccia).
-- **Messaggio d'errore semplice per l'utente, in inglese** (`DOWNLOAD_FAILED_MESSAGE`):
+- **Detailed diagnostics moved to the logs only** (removed the `urllib` probe
+ from the interface).
+- **Simple error message for the user, in English** (`DOWNLOAD_FAILED_MESSAGE`):
> *"Could not download the will-executors list. This is usually caused by
> your internet connection or a firewall, not by the plugin. Please check
> your connection (a VPN often helps) and try again."*
-### File toccati (solo presentazione/GUI, logica invariata)
+### Files touched (presentation/GUI only, logic unchanged)
-- `gui/qt/window.py` — helper condiviso `fetch_will_executors_list`,
- `download_list` con `TaskThread` + `BalWaitingDialog`, costante
+- `gui/qt/window.py` — shared helper `fetch_will_executors_list`,
+ `download_list` with `TaskThread` + `BalWaitingDialog`, constant
`DOWNLOAD_FAILED_MESSAGE`.
-- `gui/qt/lists.py` — `WillExecutorWidget.download_list` instradato sul
- percorso condiviso con `on_success` che aggiorna/salva la lista.
-- `gui/qt/dialogs.py` — `BalDialog.closeEvent`/`hideEvent` **non** fermano più
- il thread; `BalWaitingDialog.exe()` torna a `self.exec()` + `bring_to_front`.
-- `tests/gui_fixes_test.py` — asserzione di **regressione**: verifica che
- `closeEvent`/`hideEvent` **non** contengano `stop_thread` (per non
- reintrodurre il bug che scartava il download).
+- `gui/qt/lists.py` — `WillExecutorWidget.download_list` routed onto the shared
+ path with an `on_success` that updates/saves the list.
+- `gui/qt/dialogs.py` — `BalDialog.closeEvent`/`hideEvent` no longer stop the
+ thread; `BalWaitingDialog.exe()` goes back to `self.exec()` + `bring_to_front`.
+- `tests/gui_fixes_test.py` — **regression** assertion: verifies that
+ `closeEvent`/`hideEvent` do **not** contain `stop_thread` (so as not to
+ reintroduce the bug that discarded the download).
---
-## 11. Confronto strutturale finale (originale Gitea → refactor)
+## 11. Final structural comparison (original Gitea → refactor)
-Conteggio file `.py` (escluse cartelle generate):
+`.py` file count (excluding generated folders):
-| Originale (Gitea) | righe | → | Refactor (`bal/`) | righe |
+| Original (Gitea) | lines | → | Refactor (`bal/`) | lines |
|------------------------------|------:|----|-------------------------------------------|------:|
| `__init__.py` | 1 | → | `__init__.py` | 37 |
| `bal.py` | 161 | → | `core/plugin_base.py` | 351 |
@@ -362,56 +360,56 @@ Conteggio file `.py` (escluse cartelle generate):
| `heirs.py` | 792 | → | `core/heirs.py` | 806 |
| `will.py` | 903 | → | `core/will.py` | 938 |
| `willexecutors.py` | 547 | → | `core/willexecutors.py` | 390 |
-| `qt.py` (monolite GUI) | 3777 | → | suddiviso in `gui/qt/*` (vedi sotto) | — |
+| `qt.py` (GUI monolith) | 3777 | → | split into `gui/qt/*` (see below) | — |
| `bal_resources.py` | 14 | → | `bal_resources.py` | 14 |
-| `wallet_util/*.py` | 275 | → | `wallet_util/*.py` (invariati) | 280 |
+| `wallet_util/*.py` | 275 | → | `wallet_util/*.py` (unchanged) | 280 |
-Suddivisione del vecchio `qt.py` (3777 righe) nei moduli GUI:
+Split of the old `qt.py` (3777 lines) into the GUI modules:
-| Modulo refactor | righe | Contenuto |
+| Refactor module | lines | Content |
|-----------------------------|------:|-----------|
-| `gui/qt/plugin.py` | 303 | classe `Plugin` (`@hook` Electrum → GUI) |
-| `gui/qt/window.py` | 1048 | `BalWindow` (controller per-wallet) |
-| `gui/qt/dialogs.py` | 1155 | finestre di dialogo + wizard |
-| `gui/qt/lists.py` | 964 | viste ad albero (eredi/preview/executor) |
-| `gui/qt/widgets.py` | 782 | widget "foglia" |
-| `gui/qt/common.py` | 157 | import condivisi + helper |
-| `gui/qt/window_utils.py` | 119 | helper finestre (NUOVO — vedi §9) |
+| `gui/qt/plugin.py` | 303 | `Plugin` class (`@hook` Electrum → GUI) |
+| `gui/qt/window.py` | 1048 | `BalWindow` (per-wallet controller) |
+| `gui/qt/dialogs.py` | 1155 | dialog windows + wizard |
+| `gui/qt/lists.py` | 964 | tree views (heirs/preview/executor) |
+| `gui/qt/widgets.py` | 782 | "leaf" widgets |
+| `gui/qt/common.py` | 157 | shared imports + helpers |
+| `gui/qt/window_utils.py` | 119 | window helpers (NEW — see §9) |
| `gui/qt/calendar.py` | 80 | `BalCalendar` |
-| `gui/qt/theme.py` | 59 | mappatura stato → colore |
-| `gui/qt/__init__.py` | 17 | init package GUI |
+| `gui/qt/theme.py` | 59 | status → colour mapping |
+| `gui/qt/__init__.py` | 17 | GUI package init |
-> Le differenze nei conteggi di righe rispetto all'originale derivano da:
-> riformattazione/commenti, separazione degli import per modulo, e spostamento
-> di funzioni tra `util.py`/`bal.py` e i nuovi moduli. **Gli algoritmi non sono
-> stati modificati.**
+> The differences in the line counts compared to the original come from:
+> reformatting/comments, separation of imports per module, and the movement
+> of functions between `util.py`/`bal.py` and the new modules. **The algorithms
+> were not modified.**
---
-## 12. Cronologia delle modifiche su GitHub
+## 12. Change history on GitHub
-- **`4198a51`** — import iniziale del refactor strutturale (v0.2.8): separazione
- `core/` (logica) vs `gui/qt/` (presentazione), packaging conforme, fix
- caricamento zip esterno, smoke test (sezioni §1-§8).
-- **`d56fa36`** — questo changelog del refactoring (in italiano).
-- **`4806997`** — `DIAGNOSI_GUI.md`: diagnosi dei bug GUI di z-order e ciclo di
- vita (Fase A).
-- **`dd6f677`** (PR **#2**, squash) — correzioni GUI **B1-B10** + fix download
- lista will-executor + `window_utils.py` + test di regressione (sezioni §9-§10).
-- **PR #3** — fix **OverflowError su Windows (anno 2038)** che rompeva le schede
- Will/Heirs e la voce di menu (sezione §13).
+- **`4198a51`** — initial import of the structural refactor (v0.2.8):
+ separation of `core/` (logic) vs `gui/qt/` (presentation), compliant
+ packaging, external-zip load fix, smoke test (sections §1-§8).
+- **`d56fa36`** — this refactoring changelog (in Italian).
+- **`4806997`** — `GUI_DIAGNOSIS.md` (originally `DIAGNOSI_GUI.md`): diagnosis
+ of the z-order and lifecycle GUI bugs (Phase A).
+- **`dd6f677`** (PR **#2**, squash) — GUI fixes **B1-B10** + will-executor list
+ download fix + `window_utils.py` + regression test (sections §9-§10).
+- **PR #3** — fix for the **OverflowError on Windows (year 2038)** that broke
+ the Will/Heirs tabs and the menu entry (section §13).
---
-## 13. CORREZIONE BUG: OverflowError su Windows (limite anno 2038)
+## 13. BUG FIX: OverflowError on Windows (year-2038 limit)
-### Sintomo (Windows 11)
-Dopo aver **riavviato Electrum** o **cambiato wallet**, le schede **Will** e
-**Heirs** sparivano e compariva una **voce di menu condensata/illeggibile**
-(icona + testo sovrapposti) sotto il logo di Electrum, accanto a *Portafogli*.
-Su Linux il problema non si manifestava.
+### Symptom (Windows 11)
+After **restarting Electrum** or **switching wallet**, the **Will** and
+**Heirs** tabs disappeared and a **condensed/illegible menu entry**
+(overlapping icon + text) appeared under the Electrum logo, next to *Wallets*.
+On Linux the problem did not occur.
-### Causa vera (dal log di Electrum dell'utente)
+### Real cause (from the user's Electrum log)
```
OverflowError: Python int too large to convert to C int
window.py __init__ -> create_heirs_tab -> WillSettingsWidget
@@ -419,196 +417,192 @@ OverflowError: Python int too large to convert to C int
-> datetime.fromtimestamp(NLOCKTIME_MAX)
```
-- `NLOCKTIME_MAX = 2**32 - 1 = 4294967295` viene usato come locktime di
- **default/sentinella**.
-- Su **Windows** `time_t` è a **32 bit**, quindi `datetime.fromtimestamp(ts)`
- solleva **`OverflowError`** per qualsiasi timestamp oltre il **2038**.
-- Su **Linux 64-bit** la stessa chiamata **funziona**: ecco perché il bug si
- vedeva solo su Windows e i test su Linux non lo intercettavano.
-- L'eccezione interrompeva `BalWindow.__init__` durante `init_menubar` /
- `load_wallet`, lasciando le schede Will/Heirs e la voce di menu **a metà
- costruzione** → l'elemento grafico condensato/illeggibile sotto il logo.
+- `NLOCKTIME_MAX = 2**32 - 1 = 4294967295` is used as the
+ **default/sentinel** locktime.
+- On **Windows** `time_t` is **32-bit**, so `datetime.fromtimestamp(ts)`
+ raises **`OverflowError`** for any timestamp beyond **2038**.
+- On **64-bit Linux** the same call **works**: that is why the bug was visible
+ only on Windows and the Linux tests did not catch it.
+- The exception interrupted `BalWindow.__init__` during `init_menubar` /
+ `load_wallet`, leaving the Will/Heirs tabs and the menu entry **half-built**
+ → the condensed/illegible graphical element under the logo.
-> Nota: i due primi tentativi di correzione (status-bar no-op e idempotenza di
-> `init_menubar_tools`) **non** centravano la causa; sono stati comunque
-> mantenuti perché innocui e leggermente migliorativi, ma il vero colpevole era
-> questo crash a monte.
+> Note: the first two correction attempts (a no-op status bar and the
+> idempotency of `init_menubar_tools`) did **not** hit the cause; they were
+> kept anyway because they are harmless and slightly improving, but the real
+> culprit was this upstream crash.
-### Fix (comportamento invariato per tutti i valori normali)
-- **`BalTimestamp._safe_fromtimestamp()`**: `datetime.fromtimestamp` con
- **clamp a INT32_MAX** (anno 2038) in caso di `OverflowError`/`OSError`/
- `ValueError`, **esattamente** come la funzione `get_max_allowed_timestamp()`
- dell'originale (workaround per Electrum issue **#6170**).
-- Usato in `to_date` / `to_timestamp` / `__str__` / `__repr__` di
+### Fix (behaviour unchanged for all normal values)
+- **`BalTimestamp._safe_fromtimestamp()`**: `datetime.fromtimestamp` with
+ a **clamp to INT32_MAX** (year 2038) on `OverflowError`/`OSError`/
+ `ValueError`, **exactly** like the original's `get_max_allowed_timestamp()`
+ function (workaround for Electrum issue **#6170**).
+- Used in `to_date` / `to_timestamp` / `__str__` / `__repr__` of
`BalTimestamp`.
-- `gui/qt/widgets.py` (`set_value`): usa il converter sicuro.
-- `core/util.py` (`timestamp_minus`): stessa protezione inline con clamp a
+- `gui/qt/widgets.py` (`set_value`): uses the safe converter.
+- `core/util.py` (`timestamp_minus`): same inline protection with a clamp to
INT32_MAX.
-I valori entro il 2038 (date assolute normali, durate relative come `90d`/`5y`)
-producono **lo stesso identico risultato** di prima.
+Values within 2038 (normal absolute dates, relative durations such as
+`90d`/`5y`) produce **exactly the same result** as before.
### Test
-- `tests/windows_overflow_test.py` riproduce il limite 32-bit di Windows
- (monkeypatch di `datetime.fromtimestamp`) e dimostra che **senza** il fix si
- ottiene lo **stesso** `OverflowError` del log, mentre **con** il fix passa.
- Verificato anche che il test **fallisce** senza il fix.
+- `tests/windows_overflow_test.py` reproduces the Windows 32-bit limit
+ (monkeypatch of `datetime.fromtimestamp`) and proves that **without** the fix
+ you get the **same** `OverflowError` as in the log, while **with** the fix it
+ passes. It was also verified that the test **fails** without the fix.
-Confermato dall'utente: **"si ora funziona"**.
+Confirmed by the user: **"yes, it works now"**.
-## 14. NUOVA FUNZIONE: invalidazione automatica al posticipo dell'eredità
+## 14. NEW FEATURE: automatic invalidation when postponing the inheritance
-### Problema
-Una transazione di eredità viene firmata con un **locktime fisso e immutabile**
-e inviata ai will-executor, che sono economicamente incentivati a trasmetterla
-(incassano le fee). Se l'utente, dopo aver firmato/inviato, **posticipa** la
-data di consegna (es. di un anno), la **vecchia** transazione gia firmata resta
-valida sui server dei will-executor. Poiche ha il locktime piu basso, un
-will-executor potrebbe trasmetterla appena scade, eseguendo l'eredita **in
-anticipo** rispetto alla nuova volonta dell'utente. La versione precedente
-**non gestiva** questo caso: il posticipo non produceva alcuna azione.
+### Problem
+An inheritance transaction is signed with a **fixed, immutable locktime** and
+sent to the will-executors, who are economically incentivized to broadcast it
+(they collect the fees). If the user, after signing/sending, **postpones** the
+delivery date (e.g. by one year), the **old** already-signed transaction
+remains valid on the will-executors' servers. Since it has the lower locktime,
+a will-executor could broadcast it as soon as it expires, executing the
+inheritance **earlier** than the user's new intent. The previous version did
+**not handle** this case: postponing produced no action at all.
-### Soluzione (Strategia B — invalidazione esplicita on-chain)
-Al posticipo di un'eredita **gia firmata e/o inviata** (stato `COMPLETE` o
-`PUSHED`), il plugin chiede di **invalidare on-chain** i fondi prima di
-ricostruire la nuova eredita. L'invalidazione spende gli stessi UTXO verso un
-nuovo indirizzo di change con `locktime = altezza corrente` (RBF), quindi e
-trasmettibile subito: una volta confermata, la vecchia transazione pre-firmata
-diventa **definitivamente inutilizzabile**, vincendo la corsa contro qualunque
-will-executor.
+### Solution (Strategy B — explicit on-chain invalidation)
+When postponing an inheritance that is **already signed and/or sent** (state
+`COMPLETE` or `PUSHED`), the plugin asks to **invalidate the funds on-chain**
+before rebuilding the new inheritance. The invalidation spends the same UTXOs
+to a new change address with `locktime = current height` (RBF), so it is
+broadcastable immediately: once confirmed, the old pre-signed transaction
+becomes **permanently unusable**, winning the race against any will-executor.
-### Dettagli tecnici
+### Technical details
- **`core/will.py`**:
- - nuova eccezione `WillPostponedException` (sottoclasse di
+ - new exception `WillPostponedException` (subclass of
`NotCompleteWillException`);
- - `check_willexecutors_and_heirs`: il confronto del locktime non usa piu
- l'entry dell'erede memorizzata (`their[2]`), che viene aggiornata in memoria
- insieme al nuovo valore al momento del posticipo e quindi risulterebbe
- sempre uguale. Ora confronta il locktime richiesto con **`w.tx.locktime`**,
- cioe il locktime **congelato** nella transazione firmata (immutabile, e
- quello che i will-executor possiedono). Tre casi: invariato → coerente;
- nuovo > tx su will firmato/inviato → `WillPostponedException`; nuovo > tx su
- will mai inviato → semplice ricostruzione (nessuna fee on-chain).
-- **`gui/qt/dialogs.py`** (`BalBuildWillDialog.task_phase1`, il percorso reale
- usato da **Tools → Prepare**): aggiunto il ramo `except WillPostponedException`
- **prima** di `NotCompleteWillException`; si comporta come il caso "will
- scaduto" e ritorna `(None, tx)` per innescare firma + broadcast
- dell'invalidazione. L'utente preme di nuovo **Prepare** per ricostruire,
- rifirmare e reinviare la nuova eredita (due passi espliciti, per maggior
- controllo).
-- **`gui/qt/window.py`** (`build_inheritance_transaction`): aggiunto lo stesso
- ramo per completezza del percorso alternativo, con messaggio esplicativo.
-- **`gui/qt/common.py`**: `WillPostponedException` esportato.
+ - `check_willexecutors_and_heirs`: the locktime comparison no longer uses the
+ stored heir entry (`their[2]`), which is updated in memory together with the
+ new value at the moment of postponement and would therefore always look
+ equal. It now compares the requested locktime with **`w.tx.locktime`**, i.e.
+ the locktime **frozen** in the signed transaction (immutable, and the one
+ the will-executors hold). Three cases: unchanged → coherent; new > tx on a
+ signed/sent will → `WillPostponedException`; new > tx on a will never sent
+ → simple rebuild (no on-chain fee).
+- **`gui/qt/dialogs.py`** (`BalBuildWillDialog.task_phase1`, the real path used
+ by **Tools → Prepare**): added the `except WillPostponedException` branch
+ **before** `NotCompleteWillException`; it behaves like the "expired will"
+ case and returns `(None, tx)` to trigger signing + broadcasting of the
+ invalidation. The user presses **Prepare** again to rebuild, re-sign and
+ re-send the new inheritance (two explicit steps, for greater control).
+- **`gui/qt/window.py`** (`build_inheritance_transaction`): added the same
+ branch for completeness of the alternative path, with an explanatory message.
+- **`gui/qt/common.py`**: `WillPostponedException` exported.
-### NUOVA COLONNA "Server" nella lista transazioni
-Per dare all'utente visibilita costante sullo stato online delle proprie
-transazioni di eredita, e stata aggiunta una colonna dedicata **"Server"** in
-`PreviewList` (`gui/qt/lists.py`), con etichetta sempre leggibile
-(`Confirmed on server`, `Sent (not checked)`, `Send failed`, `Not on server`,
-`Signed (not sent)`, `Not sent`) e **tooltip** con URL del will-executor e
-stato. Le funzioni `server_status_text()` e `server_status_tooltip()` sono in
-`gui/qt/theme.py` e riusano gli stessi flag di stato gia esistenti.
+### NEW "Server" COLUMN in the transactions list
+To give the user constant visibility on the online status of their inheritance
+transactions, a dedicated **"Server"** column was added in `PreviewList`
+(`gui/qt/lists.py`), with an always-readable label (`Confirmed on server`,
+`Sent (not checked)`, `Send failed`, `Not on server`, `Signed (not sent)`,
+`Not sent`) and a **tooltip** with the will-executor URL and status. The
+functions `server_status_text()` and `server_status_tooltip()` are in
+`gui/qt/theme.py` and reuse the same already-existing status flags.
### Test
-- I 182 test ufficiali continuano a passare; smoke test ed external-zip test
- OK; `ruff` senza nuove segnalazioni reali.
-- Verificato sui dati reali del log dell'utente: il posticipo di un'eredita
- firmata ora rileva correttamente la condizione e avvia l'invalidazione.
+- The 182 official tests keep passing; smoke test and external-zip test OK;
+ `ruff` with no new real warnings.
+- Verified against the real data from the user's log: postponing a signed
+ inheritance now correctly detects the condition and starts the invalidation.
-Confermato dall'utente: **"mi pare che funziona"**.
+Confirmed by the user: **"it seems to work"**.
-## 15. TENTATIVO E REVERT: fix doppia invalidazione al posticipo (v0.3.1 -> v0.3.2)
+## 15. ATTEMPT AND REVERT: fix for double invalidation on postpone (v0.3.1 -> v0.3.2)
-### v0.3.1 (RITIRATA)
-Per risolvere la doppia firma dell'invalidazione al posticipo, era stato
-introdotto `Will.mark_invalidated_by_tx()`, chiamato in
-`loop_broadcast_invalidating` dopo il broadcast dell'invalidazione, per marcare
-`INVALIDATED` le will che spendevano gli stessi UTXO della tx di invalidazione
-e persistere lo stato con `save_willitems`.
+### v0.3.1 (WITHDRAWN)
+To solve the double signing of the invalidation on postpone,
+`Will.mark_invalidated_by_tx()` had been introduced, called in
+`loop_broadcast_invalidating` after broadcasting the invalidation, to mark as
+`INVALIDATED` the wills that spent the same UTXOs as the invalidation tx and to
+persist the state with `save_willitems`.
-### Perche e stata ritirata
-La modifica ha introdotto una regressione grave segnalata dall'utente:
-**la lista eredita mostrava ancora le vecchie eredita e l'aggiornamento di
-eredi/date risultava incoerente**.
+### Why it was withdrawn
+The change introduced a serious regression reported by the user:
+**the inheritance list still showed the old inheritances and the update of
+heirs/dates was inconsistent**.
-Causa: `loop_broadcast_invalidating` e il punto di broadcast usato per **TUTTI**
-i tipi di invalidazione (posticipo, CheckAlive, will scaduto/anticipato), non
-solo per il posticipo. Inoltre il metodo marcava e **persisteva** lo stato
-`INVALIDATED` su tutte le will item che condividevano gli UTXO del wallet
-(tipicamente tutte). Queste will item invalidate restavano poi in memoria e su
-disco, inquinando la ricostruzione di eredi/date e lasciando vecchie voci nella
-lista.
+Cause: `loop_broadcast_invalidating` is the broadcast point used for **ALL**
+types of invalidation (postpone, CheckAlive, expired/anticipated will), not
+only for postpone. In addition, the method marked and **persisted** the
+`INVALIDATED` state on all the will items that shared the wallet's UTXOs
+(typically all of them). These invalidated will items then stayed in memory and
+on disk, polluting the rebuild of heirs/dates and leaving old entries in the
+list.
-### v0.3.2 (questa versione): REVERT completo
-- Rimosso `Will.mark_invalidated_by_tx()` da `core/will.py`.
-- Rimossa la chiamata in `gui/qt/dialogs.py` (`loop_broadcast_invalidating`):
- il metodo torna **identico** alla v0.3.0.
-- Rimossi i due test relativi; mantenuto solo l'assert di gerarchia su
- `WillPostponedException` (corretto e indipendente).
-- `core/will.py` e `gui/qt/dialogs.py` sono ora **byte-identici** alla v0.3.0
- funzionante (verificato con `git diff a394cde`).
+### v0.3.2 (this version): full REVERT
+- Removed `Will.mark_invalidated_by_tx()` from `core/will.py`.
+- Removed the call in `gui/qt/dialogs.py` (`loop_broadcast_invalidating`): the
+ method goes back to being **identical** to v0.3.0.
+- Removed the two related tests; kept only the hierarchy assertion on
+ `WillPostponedException` (correct and independent).
+- `core/will.py` and `gui/qt/dialogs.py` are now **byte-identical** to the
+ working v0.3.0 (verified with `git diff a394cde`).
-Il bug della doppia invalidazione al posticipo resta quindi **aperto** e andra
-riaffrontato in modo piu mirato (senza toccare il percorso di broadcast comune e
-senza persistere stati su will che condividono gli UTXO), previa conferma
-dell'utente. La priorita era ripristinare il comportamento corretto di
-lista/eredi/date.
+The double-invalidation-on-postpone bug therefore remains **open** and will
+have to be tackled in a more targeted way (without touching the common
+broadcast path and without persisting states on wills that share the UTXOs),
+subject to the user's confirmation. The priority was to restore the correct
+behaviour of the list/heirs/dates.
-## 16. Aggiornamenti mancati, Check/Close coerenti, e rifinitura UI (v0.3.2)
+## 16. Missed updates, consistent Check/Close, and UI polish (v0.3.2)
-### FIX 1 - Rimozione di un erede rilevata su Check / chiusura Electrum
-`core/will.py` (`check_willexecutors_and_heirs`): prima il plugin
-rilevava solo l'**aggiunta** di un erede (raise `HeirNotFoundException` quando un
-erede corrente non era piu nella will). Mancava il caso inverso: la
-**rimozione** di un erede. Aggiunto il ramo `else` che lancia
-`HeirNotFoundException` anche quando la will porta ancora un erede che non e piu
-presente nel set di eredi corrente. Cosi la ricostruzione dell'eredita scatta su
-**Check** e alla **chiusura di Electrum** (entrambi usano lo stesso percorso
-`BalBuildWillDialog.build_will_task()`), come deciso dall'utente: nessun
-aggiornamento automatico dopo la modifica, solo manuale con Check / alla
-chiusura.
+### FIX 1 - Removal of an heir detected on Check / Electrum close
+`core/will.py` (`check_willexecutors_and_heirs`): previously the plugin
+detected only the **addition** of an heir (raising `HeirNotFoundException` when
+a current heir was no longer in the will). The opposite case was missing: the
+**removal** of an heir. Added the `else` branch that raises
+`HeirNotFoundException` also when the will still carries an heir that is no
+longer present in the current heir set. This way the inheritance rebuild is
+triggered on **Check** and on **Electrum close** (both use the same
+`BalBuildWillDialog.build_will_task()` path), as decided by the user: no
+automatic update after the change, only a manual one via Check / on close.
-### FIX 2 - Check interroga i server anche per le will gia inviate
-`core/will.py` (nuovo `Will.needs_server_check(w)`) e
-`gui/qt/lists.py` (`PreviewList.check`): prima il Check interrogava i server solo
-per le will in stato `PUSHED`. Le will gia inviate ma rimaste su "New / Not sent"
-non venivano ricontrollate ("nothing to do"). Ora `needs_server_check` include
-ogni will **VALID** con un will-executor e **non ancora CHECKED**, anche se non
-in stato `PUSHED`. Stesso controllo usato sia dal pulsante Check sia da
+### FIX 2 - Check also queries the servers for already-sent wills
+`core/will.py` (new `Will.needs_server_check(w)`) and `gui/qt/lists.py`
+(`PreviewList.check`): previously Check queried the servers only for wills in
+the `PUSHED` state. Wills already sent but left at "New / Not sent" were not
+re-checked ("nothing to do"). Now `needs_server_check` includes every **VALID**
+will with a will-executor that is **not yet CHECKED**, even if not in the
+`PUSHED` state. The same check is used both by the Check button and by
`on_close`.
-### FIX 3 - Hide invalidated/replaced da finestra Impostazioni aggiornava la lista
-`core/plugin_base.py` (nuovo `sync_hide_filters()`) e
-`gui/qt/window.py` (`update_all`): le checkbox "Hide Replaced" / "Hide
-Invalidated" nella finestra Impostazioni scrivono direttamente la config
-(`BalConfig.set`) senza toccare i flag in cache `_hide_invalidated` /
-`_hide_replaced` usati dalla lista per filtrare. Risultato: la lista continuava
-a filtrare col valore vecchio finche non si riavviava Electrum. Ora
-`update_all()` chiama `sync_hide_filters()` che ri-legge i flag dalla config,
-quindi qualunque sorgente del cambiamento (toolbar o finestra Impostazioni)
-aggiorna subito la lista.
+### FIX 3 - Hide invalidated/replaced from the Settings window updated the list
+`core/plugin_base.py` (new `sync_hide_filters()`) and `gui/qt/window.py`
+(`update_all`): the "Hide Replaced" / "Hide Invalidated" checkboxes in the
+Settings window write the config directly (`BalConfig.set`) without touching the
+cached flags `_hide_invalidated` / `_hide_replaced` that the list uses to
+filter. Result: the list kept filtering with the old value until Electrum was
+restarted. Now `update_all()` calls `sync_hide_filters()`, which re-reads the
+flags from the config, so whatever the source of the change (toolbar or
+Settings window) the list updates immediately.
-### Rifinitura UI - Risultati in grassetto nel dialog "Building Will"
-`gui/qt/dialogs.py` (`BalBuildWillDialog`): i **risultati** mostrati a destra di
-ogni riga di stato (es. `Ok`, `Ko`, `Nothing to do`, `Skipped`, `Wait`,
-`Timeout`) sono ora resi in **grassetto**, mantenendo i loro colori
-(verde/rosso/giallo). Le etichette di stato a sinistra restano in peso normale.
-Modifica centralizzata negli helper `msg_ok`, `msg_error`, `msg_warning`,
-`msg_set_status`, piu le righe dei will-executor (push e check) che ora mostrano
-`Ok/Ko` e `True/False` in grassetto + colore (verde/rosso).
+### UI polish - Bold results in the "Building Will" dialog
+`gui/qt/dialogs.py` (`BalBuildWillDialog`): the **results** shown to the right
+of each status line (e.g. `Ok`, `Ko`, `Nothing to do`, `Skipped`, `Wait`,
+`Timeout`) are now rendered in **bold**, keeping their colours
+(green/red/yellow). The status labels on the left stay in normal weight. The
+change is centralized in the helpers `msg_ok`, `msg_error`, `msg_warning`,
+`msg_set_status`, plus the will-executor lines (push and check) that now show
+`Ok/Ko` and `True/False` in bold + colour (green/red).
### Test
-- 186 test ufficiali passano; smoke test, external-zip test e simulazione dei
- flussi di aggiornamento (`tests/sim_update_flows.py`) OK; `ruff` senza nuove
- segnalazioni reali (solo falsi positivi pre-esistenti da star-import).
-- Aggiunti test in `tests/test_core_will.py`:
+- 186 official tests pass; smoke test, external-zip test and the update-flow
+ simulation (`tests/sim_update_flows.py`) OK; `ruff` with no new real warnings
+ (only pre-existing false positives from star-imports).
+- Added tests in `tests/test_core_will.py`:
`test_check_heirs_unchanged_is_coherent`,
`test_check_heir_removed_triggers_rebuild`,
`test_check_heir_added_triggers_rebuild`, `test_needs_server_check`.
-Confermato dall'utente sui dati reali: dopo Sign -> Broadcast -> Check le
-transazioni gia inviate sono tornate verdi ("confirmed on server"); la lista
-torna pulita; il grassetto e l'aggiornamento delle hide-flag funzionano.
+Confirmed by the user against real data: after Sign -> Broadcast -> Check the
+already-sent transactions turned green again ("confirmed on server"); the list
+goes back clean; the bold rendering and the hide-flag update work.
## 17. UI polish and bug fix — signed-tx colour, wizard, Building Will dialog (v0.3.3)
diff --git a/DIAGNOSI_GUI.md b/DIAGNOSI_GUI.md
deleted file mode 100644
index 440f17a..0000000
--- a/DIAGNOSI_GUI.md
+++ /dev/null
@@ -1,319 +0,0 @@
-# BAL — Diagnosi dei problemi GUI (Fase A) → ✅ RISOLTI (Fase B)
-
-> **STATO: tutti i bug B1-B10 sono stati CORRETTI** e mergiati in `main`
-> (PR #2, squash `dd6f677`). La logica di business resta **byte-identica**
-> (nessuna modifica a `bal/core/*`): sono cambiati solo presentazione, parent,
-> modalità, ciclo di vita e cleanup delle finestre.
->
-> | ID | Stato | Fix applicato |
-> |----|-------|---------------|
-> | B1 | ✅ FIXED | `self.parent` → `self._bal_parent` (dialogs/lists/widgets); parent = `top_level_of(parent)` |
-> | B2 | ✅ FIXED | `.show()` → `show_on_top()` / `show_modal()` con parent corretto |
-> | B3 | ✅ FIXED | init a caldo: `_setup_window()` replica `load_wallet`, niente "restart Electrum" |
-> | B4 | ✅ FIXED | chiave finestra stabile `_window_key()` = `id(window)` |
-> | B5 | ✅ FIXED | `on_close` riscritto: niente `except:pass`, log per-step, reset stato |
-> | B6 | ✅ FIXED | `BalBlockingWaitingDialog`: `processEvents()` ripristinato |
-> | B7 | ✅ FIXED | `closeEvent/hideEvent`: `stop_thread()` + `super()` |
-> | B8 | ✅ FIXED | `closeEvent`: `stop_thread()` (stop+wait) + `super()` |
-> | B9 | ✅ FIXED | `bring_to_front()` = `raise_()` + `activateWindow()` |
-> | B10| ✅ FIXED | uso di `window.tools_menu` (API ufficiale), niente ricerca per titolo `&Tools` |
->
-> Helper centralizzati in `bal/gui/qt/window_utils.py`:
-> `top_level_of`, `bring_to_front`, `stop_thread`, `show_modal`, `show_on_top`.
-> Test di regressione: `tests/gui_fixes_test.py` (oltre a smoke + external_zip).
-
----
-
-## (Storico) Diagnosi originale
-
-Documento di sola **diagnosi**: nessuna riga di codice funzionale era stata
-modificata in Fase A. Elenca i problemi grafici/di ciclo di vita riscontrati nel
-codice, la loro **causa tecnica** e il **fix proposto**, con riferimenti riga.
-
-I due sintomi che hai segnalato:
-- **(S1)** Le finestre del plugin spariscono dietro la finestra di Electrum.
-- **(S2)** Alcuni meccanismi funzionano solo dopo aver chiuso e "ripulito"
- Electrum.
-
-Sono entrambi spiegati dai bug qui sotto.
-
----
-
-## Riepilogo (tabella)
-
-| ID | Gravità | Sintomo | File:riga | Causa breve |
-|----|---------|---------|-----------|-------------|
-| B1 | 🔴 Alta | S1 | `dialogs.py:40,69,475` | `self.parent = parent` sovrascrive il metodo `QWidget.parent()` |
-| B2 | 🔴 Alta | S1 | `window.py:148,936`, `window.py:566` | dialoghi aperti con `.show()` (non-modali, senza stare in primo piano) |
-| B3 | 🔴 Alta | S2 | `plugin.py:38-42` | messaggio "Please restart Electrum" = init a caldo non gestito |
-| B4 | 🔴 Alta | S2 | `plugin.py:45,111` | chiave dizionario `winId` (metodo) invece di `winId()` (valore) |
-| B5 | 🟠 Media | S2 | `window.py:664-677` | `on_close` con `except: pass` che nasconde errori di cleanup |
-| B6 | 🟠 Media | S1/S2 | `dialogs.py:445-462` | `BalBlockingWaitingDialog` blocca il thread GUI, `processEvents` commentato |
-| B7 | 🟠 Media | S2 | `dialogs.py:48-58` | `closeEvent/hideEvent` con cleanup thread commentato |
-| B8 | 🟠 Media | S2 | `dialogs.py:828-830` | `closeEvent` chiama `thread.stop()` ma non `thread.wait()` né `super()` |
-| B9 | 🟡 Bassa | S1 | `dialogs.py:1121-1122` | `show()+raise_()` senza `activateWindow()` né modalità |
-| B10| 🟡 Bassa | — | `plugin.py:36` (init), vari | gestione finestre multiple/ multi-wallet fragile |
-
----
-
-## Dettaglio dei problemi
-
-### B1 — `self.parent = parent` rompe il sistema di finestre di Qt 🔴
-**Dove:** `dialogs.py:40` (in `BalDialog.__init__`), ripetuto a `:69` e `:475`;
-analoghi in altri dialoghi.
-
-```python
-self.parent = parent # <-- PROBLEMA
-super().__init__(parent)
-```
-
-**Causa:** in Qt, `parent()` è un **metodo** di `QWidget` che restituisce il
-widget genitore. Assegnando un **attributo** `self.parent`, lo si maschera: da
-quel punto `self.parent` non è più il metodo ma il valore salvato. Qualunque
-codice (anche interno a Qt o di Electrum) che si aspetta `widget.parent()` come
-metodo può comportarsi in modo imprevisto. Inoltre il `parent` passato non è
-sempre la **top-level window** corretta, quindi il dialogo non viene agganciato
-gerarchicamente alla finestra di Electrum e finisce **dietro** (S1).
-
-**Fix proposto:**
-- Non sovrascrivere `parent`: rinominare l'attributo (es. `self._bal_parent`).
-- Passare sempre come `parent` la **top-level window** di Electrum
- (`window.top_level_window()`), così il dialogo resta in primo piano rispetto
- ad essa.
-
----
-
-### B2 — Dialoghi aperti con `.show()` invece che modali 🔴
-**Dove:**
-- `window.py:148` `show_willexecutor_dialog` → `self.willexecutor_dialog.show()`
-- `window.py:936` `preview_modal_dialog` → `self.dw.show()` (il nome dice
- "modal" ma usa `show()`!)
-- `window.py:566` `show_transaction_real` → `d.show()`
-
-**Causa:** `show()` apre una finestra **non-modale e indipendente**: se il
-`parent` non è impostato correttamente (vedi B1), la finestra non resta sopra
-Electrum e ci "sparisce dietro" (S1). Si nota l'incoerenza: altrove si usa
-correttamente `.exec()` (es. `init_wizard` a `window.py:144`, `settings_dialog`
-a `plugin.py:254`), che è modale e resta in primo piano.
-
-**Fix proposto:**
-- Per i dialoghi che devono restare in primo piano: usare `exec()` (modale) **o**
- `show()` + parent corretto + `setWindowModality(Qt.WindowModal)` +
- `raise_()` + `activateWindow()`.
-- Mantenere la stessa logica di "cosa fa il dialogo" (nessun cambio di
- comportamento funzionale, solo z-order/modalità).
-
----
-
-### B3 — "Please restart Electrum to activate the BAL plugin" 🔴
-**Dove:** `plugin.py:38-42` (hook `init_qt`).
-
-```python
-if wallet:
- window.show_warning(_("Please restart Electrum to activate the BAL plugin"), ...)
- return
-```
-
-**Causa:** quando il plugin viene **abilitato a caldo** (wallet già aperto),
-l'hook `init_qt` si arrende e chiede il riavvio invece di inizializzare le tab
-e i menu sul wallet già caricato. È **la causa diretta del sintomo S2**: "devi
-chiudere/riavviare Electrum perché funzioni".
-
-**Fix proposto:**
-- In `init_qt`, se c'è già un wallet aperto, eseguire la stessa inizializzazione
- che normalmente avviene in `load_wallet` (creare `BalWindow`, tab, menu,
- caricare il will) **senza** richiedere il riavvio.
-- Simmetricamente, gestire bene `close_wallet` per smontare tab/menu, così
- ri-abilitare/ricaricare non lascia stato sporco.
-
----
-
-### B4 — Chiave del dizionario `winId` (metodo) invece di `winId()` 🔴
-**Dove:** `plugin.py:45` (scrittura) e `plugin.py:111` (lettura).
-
-```python
-self.bal_windows[top_level_window.winId] = w # scrive con la *funzione* winId
-...
-w = self.bal_windows.get(window.winId, None) # legge con la *funzione* winId
-```
-
-**Causa:** `winId` senza parentesi è il **metodo legato** (bound method), non
-l'identificatore della finestra. Usato come chiave "funziona per caso" perché
-lo stesso oggetto-finestra produce lo stesso bound method; ma è fragile e
-semanticamente errato: con più finestre/wallet o dopo riaperture la
-corrispondenza può saltare, creando `BalWindow` duplicati o non trovando quello
-giusto → stato incoerente (contribuisce a S2).
-
-**Fix proposto:**
-- Usare una chiave stabile e corretta, es. `int(window.winId())` oppure
- `id(window)`, in modo **coerente** sia in scrittura sia in lettura.
-
----
-
-### B5 — `on_close` ingoia tutti gli errori 🟠
-**Dove:** `window.py:664-677`.
-
-```python
-def on_close(self):
- try:
- if not self.disable_plugin:
- close_window = BalBuildWillDialog(self)
- close_window.build_will_task()
- self.save_willitems()
- self.heirs_tab.close()
- ...
- except Exception:
- pass # <-- nasconde qualsiasi errore di cleanup
-```
-
-**Causa:** se una qualsiasi di queste operazioni fallisce, l'eccezione viene
-silenziata: tab/menu non vengono rimossi, lo stato (`willitems`, `heirs`, tab)
-resta in memoria e "sporco" finché non si riavvia Electrum (S2).
-
-**Fix proposto:**
-- Non silenziare: loggare l'errore con `_logger`.
-- Rendere il cleanup **robusto e idempotente** (ogni passo in un try/except
- separato con log), così un fallimento parziale non blocca gli altri passi.
-- Azzerare esplicitamente lo stato (`willitems={}`, riferimenti a tab/menu a
- `None`) a fine `on_close`.
-
----
-
-### B6 — `BalBlockingWaitingDialog` blocca il thread della GUI 🟠
-**Dove:** `dialogs.py:445-462`.
-
-```python
-self.show()
-# QCoreApplication.processEvents() # <-- commentato
-# QCoreApplication.processEvents()
-try:
- task() # esegue il task SUL thread GUI -> finestra "congelata"
-finally:
- self.accept()
-```
-
-**Causa:** dopo `show()` non si dà alla GUI il tempo di disegnarsi
-(`processEvents` è commentato) e poi si esegue `task()` **bloccando** il thread
-dell'interfaccia. Risultato: la finestra "Please wait" può apparire vuota,
-non ridisegnarsi, e l'app sembra bloccata (contribuisce a S1/percezione di
-freeze).
-
-**Fix proposto:**
-- O eseguire il task in un `TaskThread` (come fa già `BalWaitingDialog`),
-- oppure, se deve restare bloccante, ripristinare un `processEvents()` dopo
- `show()` per far disegnare la finestra prima del task.
-
----
-
-### B7 — `closeEvent`/`hideEvent` con cleanup thread commentato 🟠
-**Dove:** `dialogs.py:48-58` (`BalDialog`).
-
-```python
-def closeEvent(self, event):
- self._stopping = True
- #if self.thread:
- # self.thread.stop() # <-- disattivato
- super().closeEvent(event)
-```
-
-**Causa:** alla chiusura del dialogo i thread eventualmente attivi **non**
-vengono fermati. Restano in esecuzione in background, possono scrivere su widget
-già distrutti o tenere risorse/connessioni → comportamenti erratici finché non
-si riavvia (S2).
-
-**Fix proposto:**
-- Ripristinare in modo sicuro lo stop dei thread: `if self.thread:
- self.thread.stop(); self.thread.wait()` con guardia su `None`.
-
----
-
-### B8 — `BalBuildWillDialog.closeEvent` incompleto 🟠
-**Dove:** `dialogs.py:828-830`.
-
-```python
-def closeEvent(self, event):
- self._stopping = True
- self.thread.stop()
- # manca self.thread.wait() e manca super().closeEvent(event)
-```
-
-**Causa:** `stop()` segnala lo stop ma non attende la fine del thread
-(`wait()`), e non viene chiamato `super().closeEvent(event)`: l'evento di
-chiusura non è propagato correttamente. Possibili thread orfani e finestre che
-non si chiudono pulite.
-
-**Fix proposto:**
-- `self.thread.stop(); self.thread.wait(); super().closeEvent(event)` con
- guardia su `self.thread is None`.
-
----
-
-### B9 — `show()+raise_()` senza `activateWindow()`/modalità 🟡
-**Dove:** `dialogs.py:1121-1122` (es. `WillExecutorDialog`/dettaglio).
-
-```python
-self.show()
-self.raise_()
-# manca self.activateWindow(); nessuna modalità impostata
-```
-
-**Causa:** `raise_()` alza la finestra nello stack ma su alcuni window manager
-(incluso Windows) senza `activateWindow()` non riceve il focus e può comunque
-finire dietro. Senza modalità, l'utente può tornare alla finestra principale
-lasciando il dialogo nascosto.
-
-**Fix proposto:**
-- Aggiungere `self.activateWindow()` dopo `raise_()`, e valutare
- `setWindowModality(Qt.WindowModal)` dove ha senso.
-
----
-
-### B10 — Gestione finestre multiple / multi-wallet fragile 🟡
-**Dove:** `plugin.py:30-62` (`init_qt`), `get_window` (`plugin.py:109-115`).
-
-**Causa:** la mappa `bal_windows` e l'aggancio ai menu si basano su assunzioni
-(B4) e sull'iterazione dei figli del menubar per nome (`"&Tools"`), che è
-sensibile alla **localizzazione** (tu usi `Locale: Italian_Italy`!). Se il menu
-non si chiama esattamente `&Tools` nella lingua corrente, l'aggancio può
-fallire silenziosamente.
-
-**Fix proposto:**
-- Usare l'API ufficiale `window.tools_menu` (già usata in `init_menubar`,
- `plugin.py:79`) invece di cercare il menu per titolo tradotto.
-- Unificare la creazione/lookup di `BalWindow` su una chiave stabile (B4).
-
----
-
-## Strategia di correzione proposta (per la Fase B/C)
-
-Per **non cambiare la logica di funzionamento** e ridurre i rischi, propongo di
-introdurre un **unico punto centralizzato** di gestione finestre (un piccolo
-helper, es. `gui/qt/window_utils.py`) con funzioni tipo:
-
-- `show_modal(dialog)` → imposta parent corretto, modalità, `exec()`.
-- `show_on_top(dialog)` → `show()` + `raise_()` + `activateWindow()` per i
- pochi casi che devono restare non-modali.
-
-E poi sostituire i `.show()`/`.exec()` sparsi con queste funzioni. Vantaggi:
-- la **logica di business resta intatta** (cosa fa il dialogo non cambia);
-- si tocca **solo** il "come" viene mostrato/chiuso;
-- più facile da testare e da revisionare (diff piccolo e localizzato).
-
-### Ordine consigliato
-1. **B3 + B4** (init a caldo + chiave finestre): risolvono la radice di S2.
-2. **B1 + B2 + B9** (parent/modalità/z-order): risolvono S1.
-3. **B5 + B7 + B8** (cleanup robusto + thread): chiudono i residui di S2.
-4. **B6 + B10** (waiting dialog + menu localizzati): rifiniture.
-
----
-
-## Cosa serve da te per la Fase B/C
-- Conferma che posso modificare il **comportamento della GUI** (parent,
- modalità, cleanup, init a caldo) mantenendo invariata la logica di business.
-- Test su **Electrum portable Windows** dopo ogni gruppo di fix, con descrizione
- /screenshot di cosa succede (apertura dialoghi, abilitazione a caldo,
- chiusura wallet).
-
-> Nota: i bug B1–B10 esistono **identici nell'originale** — questo refactor li
-> ha preservati fedelmente (era l'obiettivo della fase precedente). La Fase B/C
-> li corregge.
diff --git a/GUI_DIAGNOSIS.md b/GUI_DIAGNOSIS.md
new file mode 100644
index 0000000..6542435
--- /dev/null
+++ b/GUI_DIAGNOSIS.md
@@ -0,0 +1,320 @@
+# BAL — Diagnosis of the GUI problems (Phase A) → ✅ RESOLVED (Phase B)
+
+> **STATUS: all bugs B1-B10 have been FIXED** and merged into `main`
+> (PR #2, squash `dd6f677`). The business logic remains **byte-identical**
+> (no changes to `bal/core/*`): only the presentation, parent, modality,
+> lifecycle and cleanup of the windows changed.
+>
+> | ID | Status | Applied fix |
+> |----|--------|-------------|
+> | B1 | ✅ FIXED | `self.parent` → `self._bal_parent` (dialogs/lists/widgets); parent = `top_level_of(parent)` |
+> | B2 | ✅ FIXED | `.show()` → `show_on_top()` / `show_modal()` with the correct parent |
+> | B3 | ✅ FIXED | hot init: `_setup_window()` replicates `load_wallet`, no more "restart Electrum" |
+> | B4 | ✅ FIXED | stable window key `_window_key()` = `id(window)` |
+> | B5 | ✅ FIXED | `on_close` rewritten: no `except:pass`, per-step logging, state reset |
+> | B6 | ✅ FIXED | `BalBlockingWaitingDialog`: `processEvents()` restored |
+> | B7 | ✅ FIXED | `closeEvent/hideEvent`: `stop_thread()` + `super()` |
+> | B8 | ✅ FIXED | `closeEvent`: `stop_thread()` (stop+wait) + `super()` |
+> | B9 | ✅ FIXED | `bring_to_front()` = `raise_()` + `activateWindow()` |
+> | B10| ✅ FIXED | use of `window.tools_menu` (official API), no lookup by the `&Tools` title |
+>
+> Helpers centralized in `bal/gui/qt/window_utils.py`:
+> `top_level_of`, `bring_to_front`, `stop_thread`, `show_modal`, `show_on_top`.
+> Regression test: `tests/gui_fixes_test.py` (in addition to smoke + external_zip).
+
+---
+
+## (Historical) Original diagnosis
+
+A **diagnosis-only** document: no line of functional code had been modified in
+Phase A. It lists the graphical/lifecycle problems found in the code, their
+**technical cause** and the **proposed fix**, with line references.
+
+The two symptoms you reported:
+- **(S1)** The plugin windows disappear behind the Electrum window.
+- **(S2)** Some mechanisms work only after closing and "cleaning up"
+ Electrum.
+
+Both are explained by the bugs below.
+
+---
+
+## Summary (table)
+
+| ID | Severity | Symptom | File:line | Short cause |
+|----|----------|---------|-----------|-------------|
+| B1 | 🔴 High | S1 | `dialogs.py:40,69,475` | `self.parent = parent` overrides the `QWidget.parent()` method |
+| B2 | 🔴 High | S1 | `window.py:148,936`, `window.py:566` | dialogs opened with `.show()` (non-modal, not staying in the foreground) |
+| B3 | 🔴 High | S2 | `plugin.py:38-42` | "Please restart Electrum" message = unhandled hot init |
+| B4 | 🔴 High | S2 | `plugin.py:45,111` | dictionary key `winId` (method) instead of `winId()` (value) |
+| B5 | 🟠 Medium | S2 | `window.py:664-677` | `on_close` with `except: pass` that hides cleanup errors |
+| B6 | 🟠 Medium | S1/S2 | `dialogs.py:445-462` | `BalBlockingWaitingDialog` blocks the GUI thread, `processEvents` commented out |
+| B7 | 🟠 Medium | S2 | `dialogs.py:48-58` | `closeEvent/hideEvent` with the thread cleanup commented out |
+| B8 | 🟠 Medium | S2 | `dialogs.py:828-830` | `closeEvent` calls `thread.stop()` but not `thread.wait()` nor `super()` |
+| B9 | 🟡 Low | S1 | `dialogs.py:1121-1122` | `show()+raise_()` without `activateWindow()` nor modality |
+| B10| 🟡 Low | — | `plugin.py:36` (init), various | fragile multiple-window / multi-wallet handling |
+
+---
+
+## Detail of the problems
+
+### B1 — `self.parent = parent` breaks Qt's window system 🔴
+**Where:** `dialogs.py:40` (in `BalDialog.__init__`), repeated at `:69` and `:475`;
+similar in other dialogs.
+
+```python
+self.parent = parent # <-- PROBLEM
+super().__init__(parent)
+```
+
+**Cause:** in Qt, `parent()` is a **method** of `QWidget` that returns the
+parent widget. By assigning an **attribute** `self.parent`, you mask it: from
+that point on `self.parent` is no longer the method but the saved value. Any
+code (even inside Qt or Electrum) that expects `widget.parent()` as a method
+may behave unexpectedly. In addition, the `parent` that is passed is not always
+the correct **top-level window**, so the dialog is not attached hierarchically
+to the Electrum window and ends up **behind** it (S1).
+
+**Proposed fix:**
+- Do not override `parent`: rename the attribute (e.g. `self._bal_parent`).
+- Always pass as `parent` Electrum's **top-level window**
+ (`window.top_level_window()`), so the dialog stays in the foreground relative
+ to it.
+
+---
+
+### B2 — Dialogs opened with `.show()` instead of modally 🔴
+**Where:**
+- `window.py:148` `show_willexecutor_dialog` → `self.willexecutor_dialog.show()`
+- `window.py:936` `preview_modal_dialog` → `self.dw.show()` (the name says
+ "modal" but it uses `show()`!)
+- `window.py:566` `show_transaction_real` → `d.show()`
+
+**Cause:** `show()` opens a **non-modal, independent** window: if the `parent`
+is not set correctly (see B1), the window does not stay above Electrum and
+"disappears behind it" (S1). The inconsistency is noticeable: elsewhere `.exec()`
+is used correctly (e.g. `init_wizard` at `window.py:144`, `settings_dialog` at
+`plugin.py:254`), which is modal and stays in the foreground.
+
+**Proposed fix:**
+- For the dialogs that must stay in the foreground: use `exec()` (modal) **or**
+ `show()` + correct parent + `setWindowModality(Qt.WindowModal)` +
+ `raise_()` + `activateWindow()`.
+- Keep the same logic of "what the dialog does" (no change of functional
+ behaviour, only z-order/modality).
+
+---
+
+### B3 — "Please restart Electrum to activate the BAL plugin" 🔴
+**Where:** `plugin.py:38-42` (`init_qt` hook).
+
+```python
+if wallet:
+ window.show_warning(_("Please restart Electrum to activate the BAL plugin"), ...)
+ return
+```
+
+**Cause:** when the plugin is **enabled hot** (wallet already open), the
+`init_qt` hook gives up and asks for a restart instead of initializing the tabs
+and menus on the already-loaded wallet. It is **the direct cause of symptom
+S2**: "you have to close/restart Electrum for it to work".
+
+**Proposed fix:**
+- In `init_qt`, if there is already an open wallet, run the same initialization
+ that normally happens in `load_wallet` (create `BalWindow`, tabs, menu, load
+ the will) **without** requiring a restart.
+- Symmetrically, handle `close_wallet` properly to tear down the tabs/menu, so
+ that re-enabling/reloading does not leave dirty state.
+
+---
+
+### B4 — Dictionary key `winId` (method) instead of `winId()` 🔴
+**Where:** `plugin.py:45` (write) and `plugin.py:111` (read).
+
+```python
+self.bal_windows[top_level_window.winId] = w # writes with the *function* winId
+...
+w = self.bal_windows.get(window.winId, None) # reads with the *function* winId
+```
+
+**Cause:** `winId` without parentheses is the **bound method**, not the window
+identifier. Used as a key it "works by accident" because the same window object
+produces the same bound method; but it is fragile and semantically wrong: with
+more windows/wallets or after reopening, the matching can break, creating
+duplicate `BalWindow` objects or failing to find the right one → inconsistent
+state (contributes to S2).
+
+**Proposed fix:**
+- Use a stable, correct key, e.g. `int(window.winId())` or `id(window)`,
+ **consistently** both when writing and when reading.
+
+---
+
+### B5 — `on_close` swallows all errors 🟠
+**Where:** `window.py:664-677`.
+
+```python
+def on_close(self):
+ try:
+ if not self.disable_plugin:
+ close_window = BalBuildWillDialog(self)
+ close_window.build_will_task()
+ self.save_willitems()
+ self.heirs_tab.close()
+ ...
+ except Exception:
+ pass # <-- hides any cleanup error
+```
+
+**Cause:** if any of these operations fails, the exception is silenced:
+tabs/menu are not removed, the state (`willitems`, `heirs`, tabs) stays in
+memory and "dirty" until Electrum is restarted (S2).
+
+**Proposed fix:**
+- Do not silence it: log the error with `_logger`.
+- Make the cleanup **robust and idempotent** (each step in a separate
+ try/except with logging), so a partial failure does not block the other
+ steps.
+- Explicitly reset the state (`willitems={}`, references to tabs/menu set to
+ `None`) at the end of `on_close`.
+
+---
+
+### B6 — `BalBlockingWaitingDialog` blocks the GUI thread 🟠
+**Where:** `dialogs.py:445-462`.
+
+```python
+self.show()
+# QCoreApplication.processEvents() # <-- commented out
+# QCoreApplication.processEvents()
+try:
+ task() # runs the task ON the GUI thread -> "frozen" window
+finally:
+ self.accept()
+```
+
+**Cause:** after `show()` the GUI is not given time to paint itself
+(`processEvents` is commented out) and then `task()` is run **blocking** the
+interface thread. Result: the "Please wait" window can appear empty, fail to
+repaint, and the app seems stuck (contributes to S1/the perception of a
+freeze).
+
+**Proposed fix:**
+- Either run the task in a `TaskThread` (as `BalWaitingDialog` already does),
+- or, if it must stay blocking, restore a `processEvents()` after `show()` so
+ the window is painted before the task.
+
+---
+
+### B7 — `closeEvent`/`hideEvent` with the thread cleanup commented out 🟠
+**Where:** `dialogs.py:48-58` (`BalDialog`).
+
+```python
+def closeEvent(self, event):
+ self._stopping = True
+ #if self.thread:
+ # self.thread.stop() # <-- disabled
+ super().closeEvent(event)
+```
+
+**Cause:** when the dialog closes, any active threads are **not** stopped. They
+keep running in the background, can write to already-destroyed widgets or hold
+resources/connections → erratic behaviour until a restart (S2).
+
+**Proposed fix:**
+- Safely restore stopping the threads: `if self.thread:
+ self.thread.stop(); self.thread.wait()` with a guard on `None`.
+
+---
+
+### B8 — `BalBuildWillDialog.closeEvent` incomplete 🟠
+**Where:** `dialogs.py:828-830`.
+
+```python
+def closeEvent(self, event):
+ self._stopping = True
+ self.thread.stop()
+ # missing self.thread.wait() and missing super().closeEvent(event)
+```
+
+**Cause:** `stop()` signals the stop but does not wait for the thread to finish
+(`wait()`), and `super().closeEvent(event)` is not called: the close event is
+not propagated correctly. Possible orphan threads and windows that do not close
+cleanly.
+
+**Proposed fix:**
+- `self.thread.stop(); self.thread.wait(); super().closeEvent(event)` with a
+ guard on `self.thread is None`.
+
+---
+
+### B9 — `show()+raise_()` without `activateWindow()`/modality 🟡
+**Where:** `dialogs.py:1121-1122` (e.g. `WillExecutorDialog`/detail).
+
+```python
+self.show()
+self.raise_()
+# missing self.activateWindow(); no modality set
+```
+
+**Cause:** `raise_()` raises the window in the stack but on some window
+managers (including Windows) without `activateWindow()` it does not receive
+focus and may still end up behind. Without modality, the user can go back to the
+main window leaving the dialog hidden.
+
+**Proposed fix:**
+- Add `self.activateWindow()` after `raise_()`, and consider
+ `setWindowModality(Qt.WindowModal)` where it makes sense.
+
+---
+
+### B10 — Fragile multiple-window / multi-wallet handling 🟡
+**Where:** `plugin.py:30-62` (`init_qt`), `get_window` (`plugin.py:109-115`).
+
+**Cause:** the `bal_windows` map and the menu attachment rely on assumptions
+(B4) and on iterating the menubar's children by name (`"&Tools"`), which is
+sensitive to **localization** (you use `Locale: Italian_Italy`!). If the menu
+is not named exactly `&Tools` in the current language, the attachment can fail
+silently.
+
+**Proposed fix:**
+- Use the official `window.tools_menu` API (already used in `init_menubar`,
+ `plugin.py:79`) instead of looking up the menu by its translated title.
+- Unify the creation/lookup of `BalWindow` on a stable key (B4).
+
+---
+
+## Proposed correction strategy (for Phase B/C)
+
+In order to **not change the operating logic** and reduce the risks, I propose
+to introduce a **single centralized point** for window management (a small
+helper, e.g. `gui/qt/window_utils.py`) with functions such as:
+
+- `show_modal(dialog)` → sets the correct parent, modality, `exec()`.
+- `show_on_top(dialog)` → `show()` + `raise_()` + `activateWindow()` for the
+ few cases that must stay non-modal.
+
+And then replace the scattered `.show()`/`.exec()` calls with these functions.
+Advantages:
+- the **business logic stays intact** (what the dialog does does not change);
+- only the "how" it is shown/closed is touched;
+- easier to test and to review (small, localized diff).
+
+### Recommended order
+1. **B3 + B4** (hot init + window key): fix the root of S2.
+2. **B1 + B2 + B9** (parent/modality/z-order): fix S1.
+3. **B5 + B7 + B8** (robust cleanup + threads): close the remaining S2 issues.
+4. **B6 + B10** (waiting dialog + localized menus): polish.
+
+---
+
+## What is needed from you for Phase B/C
+- Confirmation that I may modify the **GUI behaviour** (parent, modality,
+ cleanup, hot init) while keeping the business logic unchanged.
+- Testing on **Electrum portable Windows** after each group of fixes, with a
+ description/screenshot of what happens (opening dialogs, hot enabling,
+ closing the wallet).
+
+> Note: bugs B1–B10 exist **identically in the original** — this refactor
+> preserved them faithfully (that was the goal of the previous phase). Phase B/C
+> fixes them.
diff --git a/REPORT_NETWORKING_PARALLELO.md b/PARALLEL_NETWORKING_REPORT.md
similarity index 100%
rename from REPORT_NETWORKING_PARALLELO.md
rename to PARALLEL_NETWORKING_REPORT.md