- CHANGELOG_REFACTOR.md: translated sections 1-16 (header + §1-§16) from Italian to English; sections 17-18 were already English and left untouched. All code blocks, commit hashes, tables, names, versions and structure kept. - DIAGNOSI_GUI.md -> GUI_DIAGNOSIS.md: renamed and fully translated to English (title included), preserving code, line refs, emojis and tables. - REPORT_NETWORKING_PARALLELO.md -> PARALLEL_NETWORKING_REPORT.md: renamed only (content was already in English). - CHANGELOG.md: added entry #12 documenting this task. Documentation-only change; no plugin code touched (zip-first not applicable).
36 KiB
BAL — Refactoring report (for the original author)
This document lists all the changes made to the BAL plugin
(Bitcoin After Life) with respect to the original version 0.2.8.
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.
Verification environment: Electrum 4.7.2 + PyQt6 (the latest stable
release that exposes json_db.register_dict).
1. Structure reorganization (logic / GUI separation)
The main reported problem was that logic and graphics were mixed together, in
particular in a single qt.py file of 4131 lines.
Structure BEFORE (flat, 7 files)
BAL/
├── __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)
Structure AFTER (core/ vs gui/)
bal/
├── 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) 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)
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.
Map: where the 40 classes/functions of qt.py ended up
| Class/function (orig. line) | New module |
|---|---|
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. Removals (dead / debug code) — behaviour unchanged
All the following removals were verified as unused or purely debug, so they do not alter the plugin's behaviour.
-
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)
-
bal.py→core/plugin_base.py: removed the empty stub functionget_will_settings(x)(orig. lines 12-14):def get_will_settings(x): # print(x) pass⚠️ Verified: it was not referenced by any
register_dict— the threeregister_dictcalls usetuple,dict,lambda x: x. So it was dead code. The used functionget_will(x)was kept identical. -
will.py(WillItem) → moved togui/qt/theme.py: the methodWillItem.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 functionstatus_color(will_item)ingui/qt/theme.py. Verified byte-identical across all status combinations (sameget_status(...)chain, same colour codes).
3. Import changes (required by the new structure)
The imports were updated from "flat" to "package" style. Examples:
| Before | After |
|---|---|
from .bal import BalPlugin |
from .plugin_base import BalPlugin (in willexecutors) |
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 |
- Added
from .common import _, _loggerin the GUI modules, becauseimport *does not export names starting with an underscore. - Added 3 "lazy" imports (inside the functions) in
dialogs.pyto break thedialogs ↔ listscycle (lists importsBalBuildWillDialogfrom dialogs).
The internal logic of the methods was not touched: prepare_transactions(),
buildTransactions(), etc. are verbatim.
4. Electrum-standard packaging
manifest.json made compliant with https://plugins.electrum.org/developers.html :
| Field | Before | After |
|---|---|---|
name |
"BAL" |
"bal" (lowercase = dir name) |
version |
(absent, was only in the description) | "0.2.8" |
description |
with HTML <br> |
clean text |
licence |
(absent) | "MIT" |
fullname/author/available_for/icon |
present | unchanged |
__init__.py(was empty): now contains the architecture docstring and__version__ = "0.2.8".- Brought into the package:
LICENSE,VERSION,README.md,bal_resources.py, and thewallet_util/folder (unchanged).
5. BUG FIX: loading as an external plugin (.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'
- Cause: Electrum loads external plugins from zip under the synthetic
package
electrum_external_plugins.bal, and runs only the package__init__and theqtmodule. It does not register the synthetic root package nor the nested sub-packages (gui,gui.qt). A simplefrom .gui.qt.plugin import Pluginfails when walking up to the missing parents. - Fix:
qt.pyis now a resilient shim that (1) detects its own package name at runtime (__package__), (2) rebuilds insys.modulesany missing parent packages, (3) importsPluginwithimportlib.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
- Cause: some Electrum portable builds on Windows fail to decompress with
zipimportarchives 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. Tests added
tests/smoke_test.py— checks imports + basic behaviour (BalTimestamp,Utilhelpers,HEIR_*constants,WillItemstates,Pluginhooks).tests/external_zip_test.py— faithfully reproduces Electrum's loading sequence for an external plugin from zip (regression test for Bug 5a/5b).
All tests pass under Electrum 4.7.2 + PyQt6.
7. Summary: what did NOT change
- 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. Notes / recommendations
- The plugin requires Electrum 4.7.2:
json_db.register_dictwas removed in later versions (master), where it should be replaced withstored_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. GUI FIXES — windows and lifecycle (B1-B10)
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.
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 | Problem (present in the original) | Applied fix |
|---|---|---|
| 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 |
New module: gui/qt/window_utils.py (119 lines)
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)— 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 aTaskThread;show_modal(dialog)— correct modal opening (exec());show_on_top(window)— non-modal opening but above the other windows.
gui/qt/common.py imports these helpers and makes them available to the rest
of the GUI.
10. BUG FIX: will-executor list download
After installing the package with the GUI fixes, the user reported that the will-executor "download list" command no longer downloaded the list.
Investigation
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).
During the investigation, two real defects introduced by the GUI fixes were nevertheless found and corrected, which could "lose" the download result:
BalDialog.closeEvent/hideEventstopped theTaskThread. In Electrum,TaskThread.on_donerunscb_done(i.e.self.accept, which closes the dialog) beforecb_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).BalWaitingDialog.exe()used the wrong modality (show_modal/WindowModal). → Restored the originalself.exec(), first addingbring_to_front(self)to guarantee the foreground.
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.
Real cause of the failed download: environmental, NOT the plugin
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.
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.
Final cleanup (chosen by the user — "Option 1")
-
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 hardcodedhttps://welist.bitcoin-after.life/. -
Detailed diagnostics moved to the logs only (removed the
urllibprobe 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."
Files touched (presentation/GUI only, logic unchanged)
gui/qt/window.py— shared helperfetch_will_executors_list,download_listwithTaskThread+BalWaitingDialog, constantDOWNLOAD_FAILED_MESSAGE.gui/qt/lists.py—WillExecutorWidget.download_listrouted onto the shared path with anon_successthat updates/saves the list.gui/qt/dialogs.py—BalDialog.closeEvent/hideEventno longer stop the thread;BalWaitingDialog.exe()goes back toself.exec()+bring_to_front.tests/gui_fixes_test.py— regression assertion: verifies thatcloseEvent/hideEventdo not containstop_thread(so as not to reintroduce the bug that discarded the download).
11. Final structural comparison (original Gitea → refactor)
.py file count (excluding generated folders):
| Original (Gitea) | lines | → | Refactor (bal/) |
lines |
|---|---|---|---|---|
__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 (GUI monolith) |
3777 | → | split into gui/qt/* (see below) |
— |
bal_resources.py |
14 | → | bal_resources.py |
14 |
wallet_util/*.py |
275 | → | wallet_util/*.py (unchanged) |
280 |
Split of the old qt.py (3777 lines) into the GUI modules:
| Refactor module | lines | Content |
|---|---|---|
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 | status → colour mapping |
gui/qt/__init__.py |
17 | GUI package init |
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.pyand the new modules. The algorithms were not modified.
12. Change history on GitHub
4198a51— initial import of the structural refactor (v0.2.8): separation ofcore/(logic) vsgui/qt/(presentation), compliant packaging, external-zip load fix, smoke test (sections §1-§8).d56fa36— this refactoring changelog (in Italian).4806997—GUI_DIAGNOSIS.md(originallyDIAGNOSI_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. BUG FIX: OverflowError on Windows (year-2038 limit)
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.
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
-> on_locktime_change -> BalTimestamp.to_date
-> datetime.fromtimestamp(NLOCKTIME_MAX)
NLOCKTIME_MAX = 2**32 - 1 = 4294967295is used as the default/sentinel locktime.- On Windows
time_tis 32-bit, sodatetime.fromtimestamp(ts)raisesOverflowErrorfor 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__duringinit_menubar/load_wallet, leaving the Will/Heirs tabs and the menu entry half-built → the condensed/illegible graphical element under the logo.
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 (behaviour unchanged for all normal values)
BalTimestamp._safe_fromtimestamp():datetime.fromtimestampwith a clamp to INT32_MAX (year 2038) onOverflowError/OSError/ValueError, exactly like the original'sget_max_allowed_timestamp()function (workaround for Electrum issue #6170).- Used in
to_date/to_timestamp/__str__/__repr__ofBalTimestamp. gui/qt/widgets.py(set_value): uses the safe converter.core/util.py(timestamp_minus): same inline protection with a clamp to INT32_MAX.
Values within 2038 (normal absolute dates, relative durations such as
90d/5y) produce exactly the same result as before.
Test
tests/windows_overflow_test.pyreproduces the Windows 32-bit limit (monkeypatch ofdatetime.fromtimestamp) and proves that without the fix you get the sameOverflowErroras in the log, while with the fix it passes. It was also verified that the test fails without the fix.
Confirmed by the user: "yes, it works now".
14. NEW FEATURE: automatic invalidation when postponing the inheritance
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.
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.
Technical details
core/will.py:- new exception
WillPostponedException(subclass ofNotCompleteWillException); 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 withw.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).
- new exception
gui/qt/dialogs.py(BalBuildWillDialog.task_phase1, the real path used by Tools → Prepare): added theexcept WillPostponedExceptionbranch beforeNotCompleteWillException; 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:WillPostponedExceptionexported.
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
- The 182 official tests keep passing; smoke test and external-zip test OK;
ruffwith 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.
Confirmed by the user: "it seems to work".
15. ATTEMPT AND REVERT: fix for double invalidation on postpone (v0.3.1 -> v0.3.2)
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.
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.
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 (this version): full REVERT
- Removed
Will.mark_invalidated_by_tx()fromcore/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.pyandgui/qt/dialogs.pyare now byte-identical to the working v0.3.0 (verified withgit diff a394cde).
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. Missed updates, consistent Check/Close, and UI polish (v0.3.2)
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 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 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.
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 official tests pass; smoke test, external-zip test and the update-flow
simulation (
tests/sim_update_flows.py) OK;ruffwith 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.
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)
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.