Allow heirs to produce an OP_RETURN output instead of a regular BTC
payment. An address prefixed with OP_RETURN: carries hex data (max 80
bytes); such heirs always have amount 0 and are excluded from amount
calculations. GUI dialog shows a message field with the decoded text,
the heir list displays the decoded message, and the OP_RETURN output
is emitted with zero value in inheritance transactions.
- #04: Raw/Date selector now reappears on the WILL/HEIR tabs when switching to
ADVANCED (and when a raw value is pushed from the wizard), keeping the current
value/editor. New BalTimeEditWidget.apply_user_type_visibility() wired into
WillSettingsWidget for both the locktime and check-alive boxes.
- #3: Building Will report window minimum height 500 -> 450 px.
- #5: 'User Type' setting moved to the bottom, above 'Rebroadcast transactions'.
- #6: enabling ADVANCED now requires typing 'at My Risk' (case-insensitive);
wrong phrase or cancel reverts to BASIC. QInputDialog added to common import.
- #7a: on CHECK with an emptied wallet, an extra reassuring line is shown on
'Checking your will': 'Inheritance already executed (on blockchain)' in GREEN
(CONFIRMED) or 'Inheritance in mempool (waiting confirmation)' in ORANGE
(MEMPOOL). New _executed_inheritance_status() helper.
- #7b: 'Balance is too low, or CheckAlive is in the past. Skipped' (space added)
recoloured to ORANGE instead of red.
- Reset button renamed 'Reset setting' -> 'Reset to Default Setting'.
- Added tests/test_group_h_v048.py (8 tests). Version 0.4.7 -> 0.4.8.
Full suite: 266 passed; ruff clean; CHANGELOG #24; memory updated (#04 done).
Owner-approved changes after testing v0.4.6, plus accumulated v0.4.x work,
task-tracking notes, and an updated project HANDOFF document.
The four v0.4.7 changes:
1. Report area (BalBuildWillDialog) opens 500px tall (min) up to 700px (max),
then the scrollbar takes over. Previously it opened ~140px (too short).
2. Heirs are listed ONE per line again (green, bold) in _build_success_report,
reverting the v0.4.6 single-line form. Heir names can be long and the report
now scrolls, so compression is no longer needed.
3. Two wizard texts get an explicit line break: after "(or backup)" in the date
hint and after "miner fees" in the fee note (widgets.py).
4. ALL-DUST guard: when EVERY heir's share is below the Bitcoin dust limit, the
inheritance would pay nobody. Heirs.prepare_lists now raises
HeirAmountIsDustException at the end (where all heirs across all locktimes
are known with their final dust state), and dialogs.task_phase1 shows a clear
RED message and stops without building/signing/checking. A mix of dust +
valid heirs keeps building normally. The guard is intentionally in
prepare_lists, NOT prepare_transactions (which only sees the lowest locktime
and would false-positive). HeirAmountIsDustException is imported in common.py.
Tests: 3 new tests in test_core_heirs_extra.py pin the dust behaviour (all-dust
raises; mixed continues; multi-locktime continues). Full suite: 258 passed.
ruff: no new errors. Version bumped 0.4.6 -> 0.4.7 (4 files). CHANGELOG #23.
Also adds/updates HANDOFF.md so any future AI (Claude or another model) can
resume the project with full context (rules, layout, build/test/lint, dust
logic, git flow), and records the task-tracking notes in
.agent_memory_tasks.md.
- open_or_save_calendar(): emit one VEVENT per reminder instead of a single
event with VALARM blocks. Dates come from compute_reminder_offsets(), spread
across the check-alive period, with the LAST event one day before the
locktime. Unique UID per event (bal-<wallet>-<offset>d) and numbered
summaries ' (reminder N/total)'.
- Removed the now-unused create_alarms() method (no more VALARM).
- Default save filename changed from will_event.ics to BAL_will_event.ics.
- common.py: added timedelta to the datetime import.
- plugin_base.py: updated the NUM_REMINDERS comment for the new behaviour.
- tests: replaced the VALARM E1 test with test_e1_build_separate_events_for_giovanna.
- Bumped version to 0.3.7 (4 files) and added CHANGELOG entry #13.
Tests: 239 passed. ruff: no new errors.
C2 - 'Editable dates' option (default OFF):
- new EDITABLE_DATES config; checkbox in settings dialog
- WillSettingsWidget.apply_editable_dates() re-locks/unlocks the
delivery-time and check-alive fields, called from update_all() so the
toggle takes effect immediately (same mechanism as Hide Invalidated)
- EDITABLE_DATES included in the Reset-to-defaults list
C3 - RAW date box narrowed to ~1/3 (input box 6 chars); the trailing
label that shows the computed absolute date kept at 10 chars (fixed a
truncation regression)
C4 - settings dialog:
(a) bold red warning at the top
(b) 'Reset setting' button restoring all 7 dialog settings to defaults
(single source of truth: BalConfig.default), not touching wills
(c) bold 'Support: bitcoin-after.life' link (opens via webopen)
C5 - tooltips: calendar 'Export reminder dates to your calendar (.ics)',
fee field 'Mining fee rate in sat/vByte used for the will transactions',
fee '丰' icon 'Miner fee, click for more information' (tooltip font scoped
to QPushButton so it matches the other tooltips)
C6 - Locktime column rendered in bold in the will list
tests/test_group_c_settings.py: 4 new tests (EDITABLE_DATES default/toggle,
reset restores all dialog settings incl. EDITABLE_DATES, reset leaves
unrelated config untouched). Full suite: 210 passed.
Fix and refine the BAL plugin GUI without changing business logic:
core/will.py: restore the PUSHED requirement in needs_server_check so a
signed-but-not-broadcast will is no longer server-queried and therefore
stays blue (COMPLETE) instead of turning red (CHECK_FAIL). This matches the
original Gitea check() condition.
gui/qt/widgets.py: WillSettingsWidget vertical layout now caps every row to
the widest date-row width and left-aligns them; the leading icons keep their
original HelpButton width.
gui/qt/lists.py + gui/qt/common.py: the wizard toolbar button now shows a
28x28 icon plus a bold 'Create your will' caption (QSize imported).
gui/qt/dialogs.py (BalBuildWillDialog):
- closing summary row labelled 'All done: Ok' with a blank separator above it;
- 'checking variables' capitalised to 'Checking variables' (redundant trailing
colon dropped);
- final auto-closing countdown replaced by an explicit right-aligned 'Close'
button; intermediate technical pauses kept; next-steps popup preserved.
gui/qt/window.py + core/plugin_base.py: guide show_message on build, and
sync_hide_filters() in update_all so hide flags refresh immediately.
tests: test_needs_server_check updated; added offscreen preview helpers.
Version bumped to 0.3.3. 186 tests pass; ruff clean (baseline only).
Postpone safety (Strategy B):
- A signed/sent will carries an immutable locktime; postponing the delivery
time previously did nothing, so a will-executor could still broadcast the
old (earlier-locktime) transaction and execute the inheritance too early.
- core/will.py: add WillPostponedException and detect postpone by comparing
the requested locktime against w.tx.locktime (the locktime frozen in the
signed transaction) instead of the in-memory heir entry, which is updated
together with the new value and would always compare equal.
- gui/qt/dialogs.py (BalBuildWillDialog.task_phase1, the real path used by
Tools -> Prepare): handle WillPostponedException before NotCompleteWill;
return (None, tx) to trigger sign + broadcast of the invalidation, then the
user presses Prepare again to rebuild/re-sign/re-send (two explicit steps).
- gui/qt/window.py: mirror the branch in build_inheritance_transaction with an
explanatory message; wording aligned to the 'Prepare' button.
- gui/qt/common.py: export WillPostponedException.
- A postpone on a will that was never signed/sent just rebuilds (no on-chain
fee).
Server status column:
- gui/qt/lists.py: add a dedicated 'Server' column to PreviewList with an
always-readable label and a tooltip (will-executor URL + state).
- gui/qt/theme.py: add server_status_text() and server_status_tooltip(),
reusing the existing status flags.
- gui/qt/common.py: export the new theme helpers.
Docs: update README.md, bal/README.md and CHANGELOG_REFACTOR.md.
Tests: 182 passed; smoke + external-zip OK; ruff has no new real findings.
GUI / plugin lifecycle:
- Auto-close Electrum's native 'Electrum Plugins' manager dialog after the
BAL plugin is hot-enabled. Electrum 4.7.x no longer calls the old init_qt
hook, so the close is now triggered from the create_status_bar, init_menubar
and load_wallet hooks (fired when reload_windows() recreates the window).
- Robust dialog matching (isinstance / class name / localized window title) to
cope with zipimport module-identity mismatches.
- Robust dismissal of the modal dialog (reject()/done()/close()) with a retry
schedule [400, 800, 1500] ms; if it still cannot be closed, fall back to
bringing it to the front (showNormal/raise_/activateWindow) so it never
lingers hidden in the background. Counting only visible top-levels avoids
treating an already-closed dialog as still open.
Read-only field styling:
- Paint the locked Delivery time / Check Alive date editors and the mining-fee
spinbox with a light-grey background (#f0f0f0) so the user can see at a glance
that they are not editable outside the 'Build your will' wizard; the styling
is cleared when the fields are made editable again.
Pickle/RLock crash on 'Build will':
- heirs.save() now sanitises the heirs mapping via _json_safe() before handing
it to json_db.put(), which deep-copies the value. A live runtime object
(holding a threading.RLock) slipping into an heir value previously raised
'TypeError: cannot pickle _thread.RLock object' and aborted the task; such
values are now coerced to str and logged with their path.
- init_heirs_to_locktime() coerces the locktime to a plain serializable scalar.
- log_error() now accepts both a sys.exc_info() triple and a single exception
instance, fixing the secondary 'TypeError object is not subscriptable' that
masked the real error.
* fix(gui): correct window z-order and lifecycle bugs (B1-B10)
Sintomi risolti:
- S1: le finestre del plugin sparivano dietro Electrum
- S2: alcuni meccanismi funzionavano solo dopo chiusura+pulizia di Electrum
La logica di business resta BYTE-IDENTICA (nessuna modifica a bal/core/*):
cambiano solo parent, modalità, ciclo di vita, cleanup e presentazione.
Nuovo modulo bal/gui/qt/window_utils.py con helper centralizzati:
- top_level_of, bring_to_front, stop_thread, show_modal, show_on_top
Fix per bug:
- B1: self.parent -> self._bal_parent (dialogs/lists/widgets); parent = top_level_of(parent)
- B2/B9: .show() -> show_on_top()/show_modal()/bring_to_front()
- B3: init a caldo _setup_window() replica load_wallet (niente 'restart Electrum')
- B4: chiave finestra stabile _window_key() = id(window)
- B5: on_close riscritto (no except:pass, log per-step, reset stato)
- B6: BalBlockingWaitingDialog ripristina processEvents()
- B7/B8: closeEvent/hideEvent -> stop_thread() (stop+wait) + super()
- B10: uso di window.tools_menu (no ricerca per titolo localizzato '&Tools')
Test: smoke + gui_fixes (regressione B1-B10) + external_zip tutti verdi.
Doc aggiornata: DIAGNOSI_GUI.md marca B1-B10 come FIXED.
* fix(gui): do not kill task thread on dialog close (download list regression)
The B7/B8 change added stop_thread() to BalDialog.closeEvent/hideEvent.
But Electrum's TaskThread.on_done runs cb_done (often self.accept, which
closes the waiting dialog) BEFORE cb_result (on_success, which updates the
will-executor list). Stopping/joining the thread inside closeEvent therefore
tore the thread down before on_success ran, silently dropping the downloaded
will-executor list ('Download List' appeared to do nothing).
Restore the original safe behavior: the base BalDialog no longer stops the
thread on close/hide (matching the original plugin, which deliberately left
this commented out). Long-lived dialogs that own a thread still stop it
explicitly in their own handlers.
Adds a regression test asserting BalDialog.closeEvent/hideEvent never call
stop_thread.
* fix(gui): restore modal exec for waiting dialog + surface download failures
Two changes to fix 'Download List' doing nothing:
1) BalWaitingDialog.exe() now keeps the original application-modal exec()
(only adding raise/activate for visibility). The earlier switch to
window-modal could interfere with how the TaskThread result (on_success,
which populates the will-executor list) is delivered via a queued signal
while the modal loop is spinning.
2) BalWindow.download_list now logs how many entries were received and, when
the result is empty (the core download_list swallows errors and returns {}),
shows a warning to the user instead of failing silently. This makes any
future network/parse failure visible in the Electrum log and to the user.
Business logic in bal/core/* is unchanged.
* fix(gui): show the real download error reason in the warning popup
When 'Download List' fails, the core download_list returns {} and the cause
was only visible in the Electrum log (which is hard to capture). The GUI now
re-issues the same raw request when the result is empty and shows the actual
exception/reason in the warning popup (e.g. SSL error, timeout, empty server
response). Pure GUI-side diagnostics; bal/core/* logic unchanged.
* fix(gui): download will-executor list synchronously like the original
ROOT CAUSE: the original plugin's 'Download List' button called
Willexecutors.download_list() DIRECTLY on the GUI thread
(qt.py: WillExecutorWidget.download_list). The refactor instead routed the
button through BalWaitingDialog + TaskThread. Electrum's
Network.send_http_on_proxy behaves differently depending on the calling
thread, and on the user's setup the TaskThread path timed out ('No response
from the server'), while the original direct call worked fine.
FIX: WillExecutorWidget.download_list now performs the same direct,
synchronous download as the original, updates and saves the list, and shows a
warning only on genuine failure. The wizard download path (which the original
also ran via TaskThread) is left unchanged.
Compared against upstream original source (kaibot/bal-electrum-plugin):
send_request, handle_response, download_list core logic are byte-identical;
only the GUI call site is restored to the original behaviour.
* diag(gui): detailed download diagnostics + hardcoded-URL fallback
The will-executor download still times out on the user's setup even with the
direct (original-style) GUI-thread call, and the URL/request are byte-identical
to the working original. To pinpoint the real cause, the Download List button
now:
- logs whether a Network instance is present;
- tries the configured WELIST_SERVER URL AND falls back to the original
hardcoded https://welist.bitcoin-after.life/ endpoint (so a stale/bad config
value can't break it);
- shows the EXACT URL(s) tried and the precise exception per attempt in the
warning popup instead of a generic timeout message.
bal/core networking remains unchanged.
* diag(gui): add direct-HTTPS control probe to download diagnostics
When the Electrum-network download fails, also run a plain urllib HTTPS GET
(bypassing Electrum's Network/proxy layer) and show its result in the popup.
This distinguishes a real connectivity/DNS/firewall problem from an
Electrum-network-state problem, so we can finally pinpoint why the request
times out only in this build.
* fix(gui): unify wizard + button download on one synchronous path with diagnostics
The 'No response from the server (timeout?)' popup was coming from the WIZARD
download path in window.py (BalWaitingDialog + TaskThread), which was never
switched to the original direct call - only the list button had been.
Both paths now share BalWindow.fetch_will_executors_list(): a direct,
synchronous GUI-thread download (like the original), trying the configured
server then the hardcoded fallback, with full diagnostics (exact URL/error per
attempt + a direct-HTTPS control probe) shown in the failure popup.
This both fixes the wizard timeout and guarantees the same diagnostic popup
('Details (via Electrum network)' + 'Direct connection test') regardless of
which UI entry point is used.
* fix(gui): clean up will-executor download (waiting dialog + simple message)
Root cause of the 'download not working' reports was environmental (the user's
network/ISP was resetting the connection to the IPv6/IPv4 host; a VPN fixes
it), NOT a plugin bug. Final cleanup of the diagnostic code:
- download_list (button + wizard) again uses BalWaitingDialog + TaskThread so
the GUI is not frozen and shows a 'Downloading will-executors list...' dialog.
- Keep the configured + hardcoded-fallback server URLs and detailed per-attempt
diagnostics, but write them to the Electrum log only.
- On failure the user now sees a simple English message explaining it is most
likely a connection/firewall issue (a VPN often helps), instead of a technical
timeout/exception dump.
- Removed the urllib control probe from the user-facing popup.
bal/core networking unchanged.
---------
Co-authored-by: GenSpark AI Developer <ai@genspark.dev>