36 KiB
BAL — Resoconto del refactoring (per l'autore originale)
Questo documento elenca tutte le modifiche apportate al plugin BAL
(Bitcoin After Life) rispetto alla versione originale 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.
Ambiente di verifica: Electrum 4.7.2 + PyQt6 (l'ultima release stabile
che espone json_db.register_dict).
1. Riorganizzazione della struttura (separazione logica / GUI)
Il problema principale segnalato era che la logica e la grafica erano
mescolate, in particolare in un unico file qt.py da 4131 righe.
Struttura PRIMA (flat, 7 file)
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
└── bal_resources.py (14)
Struttura DOPO (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"
├── 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)
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à.
Mappa: dove sono finite le 40 classi/funzioni di qt.py
| Classe/funzione (riga orig.) | Nuovo modulo |
|---|---|
Plugin (67) |
gui/qt/plugin.py |
shown_cv (317) |
gui/qt/common.py |
BalWindow (330) |
gui/qt/window.py |
add_widget (1257) |
gui/qt/common.py |
ClickableLabel (1263) |
gui/qt/widgets.py |
BalTxFeesWidget (1271) |
gui/qt/widgets.py |
_LockTimeEditor (1340) |
gui/qt/widgets.py |
BalTimeEditWidget (1374) |
gui/qt/widgets.py |
TimeRawEditWidget (1508) |
gui/qt/widgets.py |
LockTimeRawEdit (1527) |
gui/qt/widgets.py |
LockTimeDateEdit (1605) |
gui/qt/widgets.py |
ThresholdTimeWidget (1644) |
gui/qt/widgets.py |
LockTimeWidget (1664) |
gui/qt/widgets.py |
WillSettingsWidget (1683) |
gui/qt/widgets.py |
PercAmountEdit (1818) |
gui/qt/widgets.py |
BalDialog (1883) |
gui/qt/dialogs.py |
BalWizardDialog (1913) |
gui/qt/dialogs.py |
BalWizardWidget (2002) |
gui/qt/dialogs.py |
BalWizardHeirsWidget (2068) |
gui/qt/dialogs.py |
BalWizardWEDownloadWidget (2103) |
gui/qt/dialogs.py |
BalWizardWEWidget (2190) |
gui/qt/dialogs.py |
BalWizardLocktimeAndFeeWidget(2207) |
gui/qt/dialogs.py |
BalWaitingDialog (2224) |
gui/qt/dialogs.py |
BalBlockingWaitingDialog (2285) |
gui/qt/dialogs.py |
BalLineEdit (2304) |
gui/qt/widgets.py |
BalTextEdit (2312) |
gui/qt/widgets.py |
BalCheckBox (2320) |
gui/qt/widgets.py |
BalBuildWillDialog (2335) |
gui/qt/dialogs.py |
HeirListWidget (2858) |
gui/qt/lists.py |
PreviewList (3059) |
gui/qt/lists.py |
WillDetailDialog (3445) |
gui/qt/dialogs.py |
WillWidget (3545) |
gui/qt/widgets.py |
WillExecutorListWidget (3637) |
gui/qt/lists.py |
WillExecutorWidget (3873) |
gui/qt/lists.py |
WillExecutorDialog (3982) |
gui/qt/dialogs.py |
CheckAliveError (4018) |
gui/qt/common.py |
log_error (4028) |
gui/qt/common.py |
export_meta_gui (4043) |
gui/qt/common.py |
BalCalendar (4066) |
gui/qt/calendar.py |
2. Rimozioni (codice morto / debug) — comportamento invariato
Tutte le rimozioni seguenti sono state verificate come non utilizzate o puramente di debug, quindi non alterano il comportamento del plugin.
-
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)
-
bal.py→core/plugin_base.py: rimossa la funzione stub vuotaget_will_settings(x)(orig. righe 12-14):def get_will_settings(x): # print(x) pass⚠️ Verificato: non era riferita da nessun
register_dict— i treregister_dictusanotuple,dict,lambda x: x. Quindi era codice morto. La funzione usataget_will(x)è stata mantenuta identica. -
will.py(WillItem) → spostato ingui/qt/theme.py: il metodoWillItem.get_color()(orig. riga 852) restituiva colori esadecimali — è logica di presentazione, non di dominio. È stato spostato fuori dal modello e trasformato nella funzionestatus_color(will_item)ingui/qt/theme.py. Verificato byte-identico su tutte le combinazioni di stato (stessa catena diget_status(...), stessi codici colore).
3. Cambi di import (necessari per la nuova struttura)
Gli import sono stati aggiornati da "flat" a "a package". Esempi:
| Prima | Dopo |
|---|---|
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 |
- Aggiunti
from .common import _, _loggernei moduli GUI, perchéimport *non esporta i nomi che iniziano con underscore. - Aggiunti 3 import "lazy" (dentro le funzioni) in
dialogs.pyper spezzare il ciclodialogs ↔ lists(lists importaBalBuildWillDialogda dialogs).
La logica interna dei metodi non è stata toccata: prepare_transactions(),
buildTransactions() ecc. sono verbatim.
4. Packaging conforme allo standard Electrum
manifest.json reso conforme a https://plugins.electrum.org/developers.html :
| Campo | Prima | Dopo |
|---|---|---|
name |
"BAL" |
"bal" (minuscolo = nome dir) |
version |
(assente, era solo nella description) | "0.2.8" |
description |
con <br> HTML |
testo pulito |
licence |
(assente) | "MIT" |
fullname/author/available_for/icon |
presenti | invariati |
__init__.py(era vuoto): ora contiene la docstring di architettura e__version__ = "0.2.8".- Portati nel package:
LICENSE,VERSION,README.md,bal_resources.py, e la cartellawallet_util/(invariata).
5. CORREZIONE BUG: caricamento come plugin esterno (.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:
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 moduloqt. Non registra il package radice sintetico né i sotto-package annidati (gui,gui.qt). Un semplicefrom .gui.qt.plugin import Pluginfallisce risalendo ai parent mancanti. - Fix:
qt.pyora è uno shim resiliente che (1) rileva a runtime il proprio nome di package (__package__), (2) ricostruisce insys.modulesgli eventuali package padre mancanti, (3) importaPluginconimportlib.import_module. Funziona sia come plugin interno (electrum.plugins.bal) sia esterno (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
zipimportarchivi 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.
6. Test aggiunti
tests/smoke_test.py— verifica import + comportamento di base (BalTimestamp, helper diUtil, costantiHEIR_*, stati diWillItem, hook delPlugin).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).
Tutti i test passano sotto Electrum 4.7.2 + PyQt6.
7. Riepilogo: cosa NON è cambiato
- 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.
8. Note / raccomandazioni
- Il plugin richiede Electrum 4.7.2:
json_db.register_dictè stato rimosso nelle versioni successive (master), dove andrebbe sostituito constored_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.
9. CORREZIONI GUI — finestre e ciclo di vita (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.
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.
| ID | Problema (presente nell'originale) | Correzione applicata |
|---|---|---|
| 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 |
Nuovo modulo: gui/qt/window_utils.py (119 righe)
Gli helper per la gestione delle finestre sono stati centralizzati in un unico modulo, così la stessa logica non viene duplicata nei vari dialog:
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 unTaskThread;show_modal(dialog)— apertura modale corretta (exec());show_on_top(window)— apertura non modale ma sopra le altre finestre.
gui/qt/common.py importa questi helper e li rende disponibili al resto della
GUI.
10. CORREZIONE BUG: download lista will-executor
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.
Indagine
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).
Durante l'indagine sono comunque emersi e stati corretti due difetti reali introdotti dalle correzioni GUI, che potevano "perdere" il risultato del download:
BalDialog.closeEvent/hideEventfermavano ilTaskThread. In ElectrumTaskThread.on_doneeseguecb_done(cioèself.accept, che chiude il dialog) prima dicb_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).BalWaitingDialog.exe()usava una modalità sbagliata (show_modal/WindowModal). → Ripristinato l'originaleself.exec(), aggiungendo primabring_to_front(self)per garantire il primo piano.
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.
Causa vera del mancato download: ambientale, NON del 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.
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.
Pulizia finale (scelta dall'utente — "Opzione 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 hardcodedhttps://welist.bitcoin-after.life/. -
Diagnostica dettagliata spostata nei soli log (rimossa la probe
urllibdall'interfaccia). -
Messaggio d'errore semplice per l'utente, in inglese (
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)
gui/qt/window.py— helper condivisofetch_will_executors_list,download_listconTaskThread+BalWaitingDialog, costanteDOWNLOAD_FAILED_MESSAGE.gui/qt/lists.py—WillExecutorWidget.download_listinstradato sul percorso condiviso conon_successche aggiorna/salva la lista.gui/qt/dialogs.py—BalDialog.closeEvent/hideEventnon fermano più il thread;BalWaitingDialog.exe()torna aself.exec()+bring_to_front.tests/gui_fixes_test.py— asserzione di regressione: verifica checloseEvent/hideEventnon contenganostop_thread(per non reintrodurre il bug che scartava il download).
11. Confronto strutturale finale (originale Gitea → refactor)
Conteggio file .py (escluse cartelle generate):
| Originale (Gitea) | righe | → | Refactor (bal/) |
righe |
|---|---|---|---|---|
__init__.py |
1 | → | __init__.py |
37 |
bal.py |
161 | → | core/plugin_base.py |
351 |
util.py |
1051 | → | core/util.py |
614 |
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) |
— |
bal_resources.py |
14 | → | bal_resources.py |
14 |
wallet_util/*.py |
275 | → | wallet_util/*.py (invariati) |
280 |
Suddivisione del vecchio qt.py (3777 righe) nei moduli GUI:
| Modulo refactor | righe | Contenuto |
|---|---|---|
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/calendar.py |
80 | BalCalendar |
gui/qt/theme.py |
59 | mappatura stato → colore |
gui/qt/__init__.py |
17 | init package GUI |
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.pye i nuovi moduli. Gli algoritmi non sono stati modificati.
12. Cronologia delle modifiche su GitHub
4198a51— import iniziale del refactor strutturale (v0.2.8): separazionecore/(logica) vsgui/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).
13. CORREZIONE BUG: OverflowError su Windows (limite anno 2038)
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.
Causa vera (dal log di Electrum dell'utente)
OverflowError: Python int too large to convert to C int
window.py __init__ -> create_heirs_tab -> WillSettingsWidget
-> on_locktime_change -> BalTimestamp.to_date
-> datetime.fromtimestamp(NLOCKTIME_MAX)
NLOCKTIME_MAX = 2**32 - 1 = 4294967295viene usato come locktime di default/sentinella.- Su Windows
time_tè a 32 bit, quindidatetime.fromtimestamp(ts)sollevaOverflowErrorper 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__duranteinit_menubar/load_wallet, lasciando le schede Will/Heirs e la voce di menu a metà costruzione → l'elemento grafico condensato/illeggibile sotto il 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.
Fix (comportamento invariato per tutti i valori normali)
BalTimestamp._safe_fromtimestamp():datetime.fromtimestampcon clamp a INT32_MAX (anno 2038) in caso diOverflowError/OSError/ValueError, esattamente come la funzioneget_max_allowed_timestamp()dell'originale (workaround per Electrum issue #6170).- Usato in
to_date/to_timestamp/__str__/__repr__diBalTimestamp. gui/qt/widgets.py(set_value): usa il converter sicuro.core/util.py(timestamp_minus): stessa protezione inline con clamp a INT32_MAX.
I valori entro il 2038 (date assolute normali, durate relative come 90d/5y)
producono lo stesso identico risultato di prima.
Test
tests/windows_overflow_test.pyriproduce il limite 32-bit di Windows (monkeypatch didatetime.fromtimestamp) e dimostra che senza il fix si ottiene lo stessoOverflowErrordel log, mentre con il fix passa. Verificato anche che il test fallisce senza il fix.
Confermato dall'utente: "si ora funziona".
14. NUOVA FUNZIONE: invalidazione automatica al posticipo dell'eredità
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.
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.
Dettagli tecnici
core/will.py:- nuova eccezione
WillPostponedException(sottoclasse diNotCompleteWillException); 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 conw.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).
- nuova eccezione
gui/qt/dialogs.py(BalBuildWillDialog.task_phase1, il percorso reale usato da Tools → Prepare): aggiunto il ramoexcept WillPostponedExceptionprima diNotCompleteWillException; 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:WillPostponedExceptionesportato.
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.
Test
- I 182 test ufficiali continuano a passare; smoke test ed external-zip test
OK;
ruffsenza 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.
Confermato dall'utente: "mi pare che funziona".
15. TENTATIVO E REVERT: fix doppia invalidazione al posticipo (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.
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.
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.
v0.3.2 (questa versione): REVERT completo
- Rimosso
Will.mark_invalidated_by_tx()dacore/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.pyegui/qt/dialogs.pysono ora byte-identici alla v0.3.0 funzionante (verificato congit 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.
16. Aggiornamenti mancati, Check/Close coerenti, e rifinitura UI (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 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
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.
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).
Test
- 186 test ufficiali passano; smoke test, external-zip test e simulazione dei
flussi di aggiornamento (
tests/sim_update_flows.py) OK;ruffsenza nuove segnalazioni reali (solo falsi positivi pre-esistenti da star-import). - Aggiunti test 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.
17. UI polish and bug fix — signed-tx colour, wizard, Building Will dialog (v0.3.3)
This session groups several behaviour-invariant UI refinements plus one colour bug fix. All comments and code remain in English; only the chat with the author was in Italian.
FIX — Signed-but-not-sent transaction shown RED instead of blue
core/will.py (needs_server_check): a previous-session change (section 16,
"FIX 2") had removed the PUSHED requirement from needs_server_check, so a
will that was signed but never broadcast was still server-queried. The query
returned CHECK_FAIL, and because status_color() checks CHECK_FAIL (red,
#e83845) before COMPLETE (blue, #2bc8ed), the row turned red. Restored the
original Gitea check() condition by adding back and w.get_status("PUSHED"),
so only already-broadcast wills are server-checked. A signed-but-not-sent will
now stays blue (COMPLETE) as in the original.
tests/test_core_will.py(test_needs_server_check): a freshly-built item (VALID, not PUSHED) now correctly expectsFalse.
Wizard "Will Settings" — equal-width, left-aligned rows
gui/qt/widgets.py (WillSettingsWidget, vertical layout): the calendar button
and the fee field used to stretch to the dialog's right edge, far wider than the
date rows. Now every row is capped to the widest date-row width (row_w) and
left-aligned, so they form a tidy column. The leading icons keep their original
HelpButton width (icon_w is used only as a spacer in front of the calendar,
never to widen the icons themselves).
Wizard button — icon + text
gui/qt/lists.py (create_toolbar): the "build your will" toolbar button is now
more inviting: a 28×28 wizard icon plus a bold "Create your will" caption,
setMinimumHeight(40). gui/qt/common.py gained QSize in the QtCore import.
Building Will dialog — clearer final report + manual Close
gui/qt/dialogs.py (BalBuildWillDialog):
- The closing summary line is no longer a bare "Ok": it now has an explicit
left-side label,
"All done: Ok", like the other result rows. - A blank separator row is inserted above "All done" so the overall outcome is visually detached from the per-step rows.
- The four
"checking variables"status strings are capitalised to"Checking variables"to match the rows below; the redundant trailing colon on the final one was dropped (msg_set_statusalready adds":\t"). - The final auto-closing countdown (
self.wait(5)) was replaced by an explicit right-aligned "Close" button (_add_close_button/_on_close_clicked). The dialog now stays open until the user dismisses it, so the full report can be read at leisure. The intermediate technical pauses (wait(10),wait(5),wait(3)) are kept. Closing still shows the persistent "next steps" (Sign / Broadcast) popup whenself._next_steps_hintis set.
Preview helpers (dev-only, not shipped logic)
tests/preview_wizard_settings_align.py, tests/preview_wizard_button.py,
tests/preview_building_will_close_btn.py: small offscreen scripts used to
render before/after mock-ups for visual approval.
Test
- 186 official tests pass; smoke test, external-zip test OK.
ruffreports only the pre-existing baseline false positives (F401/F403/F405 star-import re-exports, one F841, one F541) — no new issues.- Version bumped to 0.3.3 (
bal/VERSION,bal/__init__.py,bal/manifest.json).
18. Documentation site (docs/) — no behaviour change
Added a docs/ tree that renders directly on GitHub and GitHub Pages (no PDF):
docs/inheritance-options.md+.html: a code‑accurate Inheritance Options Guide covering every change a user can make (date earlier/later, add/remove heir, change percentages, fees, will‑executors), each transaction status flag and its colour, and what happens on the will‑executor servers. Includes a decision flow chart (GitHub‑native Mermaid block in the.md, plus a static SVG fallbackdocs/images/inheritance-flow.svg, plus a live Mermaid render in the.html). Behaviour is derived directly fromcore/will.py::is_will_valid/check_willexecutors_and_heirsandgui/qt/window.py::build_inheritance_transaction.docs/manual/README.md+manual.html+images/: the official BAL User Manual (revB) converted from the upstream Gitea PDF into GitHub‑friendly Markdown/HTML with the original screenshots re‑rendered at high resolution (docs/manual/images/fig*.png,logo.png).docs/README.md: documentation index linking both documents.
No plugin code changed; 186 tests still pass.