- Sort imports and fix pyproject ruff config (per-file ignores for
intentional Qt/core exceptions)
- Mark Heirs.validate_* helpers as @staticmethod
- Clean up dead code, rename shadowing vars, use raise ... from
- Add AGENTS.md with env/lint/test/release guidance
Read the plugin version at runtime from bal/manifest.json via
importlib.resources (zip-safe, works from inside a zip on Windows).
Removes the four-file duplication (VERSION, __init__.py, plugin_base.py,
manifest.json) and deletes the bal/VERSION file.
- bal/core/plugin_base.py: new get_version() function, BalPlugin.version
is now a @property, removed hardcoded __version__
- bal/__init__.py: removed __version__, replaced with a comment
- bal/core/willexecutors.py: user-agent uses get_version()
- bal/gui/qt/dialogs.py, widgets.py: call sites use .version property
- tests/test_version_source.py: 7 tests incl. zip-safe subprocess check
- HANDOFF.md: updated to single-source-of-truth documentation
- CHANGELOG.md: entries 43-46
- bal/manifest.json: version bumped to 0.6.1
PR #4 (branch bitcoinafterlife-patch-5).
- #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.
UI batch (TASK A/B/C/D):
- (A) Clearer 'Will expired' message: shortened will id (8+8 chars) and
readable UTC date instead of raw UNIX timestamp; shown in WARNING colour
(orange) instead of ERROR (red) in the wizard, as it is an expected step.
- (A2) Split the orange message onto two lines via <br> (rendered as HTML by
msg_warning) to avoid an overly long single line.
- (B) History tab labels (text only): inheritance txs -> 'BAL Inheritance
transaction'; invalidate tx -> 'BAL Invalidate transaction'. Colours left
to Electrum defaults.
- (C) New 'No will-executor TX' checkbox in plugin settings, bound to the
existing NO_WILLEXECUTOR config (default ON, kept in sync with the wizard),
with help text, and included in the settings reset action.
- (D) Wizard button label 'Create your will' -> 'Build Your Will'.
Regression fix (A3):
- When an heir was added to an already-expired will via the wizard, the
automatic invalidation no longer triggered. Root cause: the inner
check_will() after build_will() raised WillExpiredException and the handler
returned (False, None), which never invalidated.
- Auto-opening the invalidation tx window from the closing wizard proved
unreliable (the window ended up behind the main wallet on some window
managers; a re-check loop could ask to invalidate repeatedly before the tx
reached the mempool). The robust fix: close the wizard and show a clear
instruction popup guiding the user to run 'Tools -> Invalidate' and then
press 'Check' to finish the will. Reasoning documented in the code.
Version bumped to 0.3.9 (manifest.json, __init__.py, plugin_base.py, VERSION).
CHANGELOG entry #15 updated. Verified: py_compile OK, ruff clean (no new
errors), full test suite 239 passed.
Rewrite the CHECK ALIVE help popup (ThresholdTimeWidget.help_text) to explain
both modes, keeping the <b>CHECK ALIVE</b> title bold:
- DATA mode: set the check-alive date; on wallet open, if the date has passed
the plugin asks whether to postpone the inheritance.
- RAW mode: when less than this time is missing, ask to invalidate; failing to
invalidate delivers the transactions to the heirs; kept the d/y suffix help.
Fix two typos within this help text: 'less then' -> 'less than',
'currrent' -> 'current'.
Bump version to 0.3.8 across the four version files. CHANGELOG entry #14.
- 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.
Two GUI fixes bundled together (v0.3.6):
- Fix#10 (black bar): in dialogs.py on_success_phase1, the cancelled
invalidation branch no longer calls the blocking self.wait(3)+self.close().
wait() uses time.sleep() and runs in the GUI thread, freezing the UI so the
'Building Will' dialog could not repaint the area it just resized, leaving a
black rectangle at the bottom. It now shows 'Aborted' plus the existing
non-blocking Close button (_add_close_button), consistent with the other
end-of-flow branches.
- Fix#11 (editable fee): in widgets.py WillSettingsWidget.apply_editable_dates,
the fee widget (baltx_fees) now follows the same 'Editable dates' setting as
the locktime/threshold widgets (set_read_only(not editable_dates)) instead of
being forced read-only. With the setting ticked, dates AND fee are editable
outside the wizard; unticked, all three are read-only. Default stays OFF.
Version bumped 0.3.5 -> 0.3.6 (manifest.json, __init__.py, plugin_base.py,
VERSION). CHANGELOG: entries #10 and #11. ruff clean; full suite 239 passed.
D1 - number of reminders (default 3, max 5):
- new NUM_REMINDERS config in plugin_base.py
- new BalSpinBox widget bound to a BalConfig
- new pure helper compute_reminder_offsets(days, count): reminders spread
uniformly across the check-alive period, always before the deadline
(offset >= 1), at most one per available day, de-duplicated, earliest
first (e.g. (30,3)->[30,16,1], (2,3)->[2,1], (1,3)->[1], (0,3)->[])
- create_alarms() rewritten to use it and emit one VALARM per offset with a
DESCRIPTION reminder text
- 'Number of reminders' spin box (range 1..5) added to the settings dialog
and to the Reset list
D1b - save-only .ics (ask where, default to Desktop):
- BalCalendar.desktop_dir() helper (~/Desktop with home fallback)
- open_or_save_calendar() no longer opens the file with a calendar app; it
always shows a 'save as' dialog (.ics filter, default will_event.ics,
starting on the Desktop) and copies the file there, then confirms.
Identical behaviour on Windows/Linux/macOS. save_to_cwd -> save_ics_to.
Follow-up: removed the now-unused 'Calendar App' field from the settings
dialog (and from the Reset list). CALENDAR_APP config and
open_with_default_app left in place (unused) to avoid unrelated changes.
D2 intentionally skipped (per user request).
tests/test_group_d_alarms.py: 7 new tests (NUM_REMINDERS default/change and
all distribution rules). Full suite: 217 passed.
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.
Bump plugin version to 0.3.4 (manifest, __init__, plugin_base, VERSION).
GROUP A
- A1: remove block-height locktimes; the plugin now uses UNIX timestamps
only. The NLOCKTIME_BLOCKHEIGHT_MAX guard is kept on purpose (it forces
every locktime to be a timestamp). chk_locktime is now 2-arg; int_locktime
and anticipate_locktime no longer accept blocks; RAW input only accepts d/y.
Two now-dormant configs (LOCKTIME_BLOCKS, LOCKTIMEDELTA_BLOCKS) are kept with
comments to avoid touching persisted keys.
- A2: rename PENDING -> MEMPOOL everywhere (label 'Mempool', yellow #ffce30);
add new UPDATED status; ANTICIPATED & UPDATED keep VALID; documented
set_status rules; backward-compat migration (old PENDING -> MEMPOOL).
- A3: clarify that anticipating to a future date only rebuilds (never
invalidates), while only a past locktime invalidates (WillExpired). Code was
already correct; only the comment and docs were fixed.
Colour follow-up: UPDATED lightened from #800080 to #b266b2 (more readable),
updated in theme.py, docs and the theme test.
GROUP B
- B1: verified the 'Create your will' button already opens the guided wizard
(no code change needed).
- B2: new persisted AUTO_SIGN setting (default ON) with an 'Auto-sign on Check'
checkbox in the settings dialog. When enabled, Check signs and broadcasts
automatically; the wallet password is requested only for encrypted wallets.
B2 follow-up (fixes reported after testing):
- Remove the duplicate sign/broadcast cycle in lists.check(); build_will_task()
already signs and broadcasts.
- Suppress the manual 'press Sign/Broadcast' hint and its popup when AUTO_SIGN
is ON (kept when OFF).
- Make broadcast one-shot: removed the retry flag and the Exception('retry');
failed will-executors stay PUSH_FAIL and are skipped (no endless retry).
PUSHED transactions are already excluded from re-collection.
Docs: inheritance-options.md/.html and inheritance-flow.svg updated to v0.3.4.
Tests: 206 passing (new test_anticipate_manual_locktime, test_anticipate_past_locktime,
test_group_b_auto_sign; updated core/util, core/will_extra, gui/theme, gui/widgets).
CHANGELOG.md added with one numbered entry per task.
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).
Revert the v0.3.1 double-invalidation change (it caused an inheritance-list
regression: stale/invalidated wills lingered and heir/date updates became
incoherent) and add several targeted missed-update fixes plus a UI refinement.
Revert (v0.3.1 -> v0.3.2):
- Remove Will.mark_invalidated_by_tx() and its call in
loop_broadcast_invalidating. core/will.py and gui/qt/dialogs.py are restored
to the working v0.3.0 behaviour. The postpone double-invalidation issue is
intentionally left open, to be addressed without touching the shared
broadcast path.
FIX 1 - detect heir removal on Check / Electrum close:
- core/will.py (check_willexecutors_and_heirs): the else-branch now raises
HeirNotFoundException when a will still carries an heir that is no longer in
the current heirs set (heir removed), mirroring the existing 'heir added'
path. Rebuild therefore triggers on Check and on_close (same build_will_task
path), as decided by the user (manual update only, no auto-rebuild).
FIX 2 - Check queries servers for already-sent wills:
- core/will.py: new Will.needs_server_check(w) returns True for any VALID will
with a will-executor that is not yet CHECKED (no longer limited to PUSHED).
- gui/qt/lists.py (PreviewList.check): use needs_server_check so wills stuck on
'New / Not sent' are re-checked instead of reporting 'nothing to do'.
FIX 3 - Settings-dialog hide toggles refresh the list:
- core/plugin_base.py: new sync_hide_filters() re-reads the cached
_hide_invalidated / _hide_replaced flags from the persisted config.
- gui/qt/window.py (update_all): call sync_hide_filters() before refreshing, so
toggling 'Hide Invalidated' / 'Hide Replaced' in the Settings dialog (which
writes the config directly) updates the transaction list immediately instead
of requiring an Electrum restart.
UI - bold results in the Building Will dialog:
- gui/qt/dialogs.py (BalBuildWillDialog): render the right-side results in bold
(Ok, Ko, Nothing to do, Skipped, Wait, Timeout, ...) keeping the left-side
state labels in normal weight. Centralised in msg_ok/msg_error/msg_warning/
msg_set_status, plus the will-executor push/check rows now show Ok/Ko and
True/False in bold + colour (green/red).
Tests/tooling:
- tests/test_core_will.py: add test_check_heirs_unchanged_is_coherent,
test_check_heir_removed_triggers_rebuild, test_check_heir_added_triggers_rebuild,
test_needs_server_check.
- tests/sim_update_flows.py: real-world update-scenario simulation.
- tests/preview_build_will_dialog.py, tests/preview_we_rows.py: GUI-only
before/after previews of the bold formatting.
- Bump version to 0.3.2 (VERSION, manifest.json, __init__.py, plugin_base.py).
186 tests pass; smoke test, external-zip test and update-flow simulation OK;
ruff reports only pre-existing star-import false positives.
When postponing the delivery time of an already signed/sent will, the user
was asked to sign the on-chain invalidation transaction twice before the new
(postponed) will could be built.
Root cause: after the invalidation tx was broadcast, on_success_invalidate
restarted task_phase1 to rebuild the will, but the old will items were still
marked COMPLETE/PUSHED with their original tx.locktime (the on-chain
invalidation did not update the in-memory status). The postpone check therefore
fired WillPostponedException a second time, requesting another invalidation.
Fix:
- Add Will.mark_invalidated_by_tx(will, tx): marks INVALIDATED every valid will
item that spends a prevout consumed by the just-broadcast invalidation tx.
Setting INVALIDATED clears the VALID flag, removing those items from
only_valid_list so the postpone/expire check no longer fires.
- Call it from loop_broadcast_invalidating after a successful broadcast (txid
obtained) and persist via save_willitems. On the phase-1 restart the old will
is no longer VALID, so the will is rebuilt directly: a single invalidation
signature followed by the new will.
Tests: add test_will_mark_invalidated_by_tx and
test_will_mark_invalidated_by_tx_no_match plus the WillPostponedException
hierarchy assertion. 184 tests pass; smoke and external-zip OK; ruff clean.
Bump version to 0.3.1.
Bump plugin version from 0.2.8 to 0.3.0 in VERSION, manifest.json,
__init__.py and core/plugin_base.py for the 0.3.0 release (postpone
invalidation safety + Server status column).
* fix(gui): voci di menu BAL duplicate/condensate dopo riavvio o cambio wallet
Sintomo (Windows 11): dopo aver riavviato Electrum o cambiato wallet, le
schede Will/Heirs sparivano dalla tab bar e dal menu, e compariva una voce
di menu condensata/illeggibile (icona + testo sovrapposti) sotto il logo di
Electrum, accanto a 'Portafogli'.
Causa: init_menubar_tools veniva eseguito DUE volte sulla stessa finestra.
Con il plugin gia abilitato, al riavvio Electrum invoca sia l'hook
init_menubar sia il percorso di init a caldo (init_qt -> _setup_window),
entrambi chiamano init_menubar_tools -> addTab/addAction duplicati.
Nell'originale init_qt faceva return (chiedendo il riavvio) e quindi i menu
venivano creati una sola volta; rimuovendo quel return (fix B3) e' emersa la
doppia inizializzazione.
Fix:
- BalWindow._menubar_initialized: guardia di idempotenza.
- init_menubar_tools: se gia inizializzato, esce subito (niente duplicati).
- on_close: resetta il flag dopo aver rimosso tab/azioni, cosi la stessa
finestra puo essere riusata per un altro wallet.
- tests/gui_fixes_test.py: regressione che verifica la guardia in __init__,
init_menubar_tools e on_close.
Logica di business invariata (nessuna modifica a bal/core/*).
* fix(gui): ripristina create_status_bar come no-op (come originale)
L'elemento di menu condensato/illeggibile sotto il logo di Electrum era
causato dal StatusBarButton aggiunto da create_status_bar.
Nell'originale Gitea questo hook aveva un 'return' subito dopo il log, PRIMA
di costruire il bottone: era quindi disabilitato di proposito. Durante la
pulizia del 'dead code' nel refactoring quel return era stato rimosso,
riattivando la creazione del bottone -> elemento icona+testo renderizzato
nel punto sbagliato dopo riavvio/cambio wallet.
Fix: create_status_bar torna a essere un no-op (return), fedele all'originale.
Le impostazioni restano raggiungibili da Strumenti -> Plugin.
Regressione: gui_fixes_test verifica che create_status_bar non chiami
addPermanentWidget.
* fix(core): OverflowError su Windows (anno 2038) che rompeva tab/menu BAL
CAUSA VERA (dal log Electrum dell'utente, Windows 11):
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)
Su Windows time_t e' a 32 bit, quindi datetime.fromtimestamp() solleva
OverflowError per qualsiasi timestamp oltre il 2038 (es. NLOCKTIME_MAX =
2**32-1 = 4294967295, usato come locktime di default/sentinella). Su Linux
64-bit la stessa chiamata funziona: per questo 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 meta' costruzione ->
l'elemento grafico condensato/illeggibile sotto il logo di Electrum.
FIX (comportamento invariato per tutti i valori normali):
- BalTimestamp._safe_fromtimestamp(): datetime.fromtimestamp con clamp a
INT32_MAX in caso di OverflowError/OSError/ValueError, esattamente come la
funzione get_max_allowed_timestamp() dell'originale (Electrum issue #6170).
- Usato in to_date / to_timestamp / __str__ / __repr__ di BalTimestamp.
- widgets.py set_value: usa il converter sicuro.
- 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.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 col fix passa. Verificato anche che il
test FALLISCE senza il fix.
* docs(it): documenta il fix OverflowError Windows (anno 2038) nel changelog
Aggiunge la sezione §13 al CHANGELOG_REFACTOR.md che descrive:
- sintomo (schede/menu rotti su Windows dopo riavvio/cambio wallet)
- causa vera dal log (datetime.fromtimestamp(NLOCKTIME_MAX) -> OverflowError
su time_t 32-bit di Windows)
- fix con _safe_fromtimestamp (clamp a INT32_MAX, come Electrum #6170)
- test di regressione windows_overflow_test.py
Aggiornata anche la cronologia (§12) con PR #3.
---------
Co-authored-by: GenSpark AI Developer <ai@genspark.dev>