feat(v0.4.7): report area 500px, heirs one-per-line, wizard line breaks, ALL-DUST guard

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.
This commit is contained in:
2026-06-28 23:02:25 -04:00
parent ed83af6be9
commit 646a33f2f5
19 changed files with 2803 additions and 323 deletions

View File

@@ -91,7 +91,7 @@ class BalPlugin(BasePlugin):
"""
_version = None
__version__ = "0.3.9" # AUTOMATICALLY GENERATED DO NOT EDIT
__version__ = "0.4.7" # AUTOMATICALLY GENERATED DO NOT EDIT
# Command used to open an .ics calendar file, per operating system.
default_app = {
@@ -190,11 +190,31 @@ class BalPlugin(BasePlugin):
# most one event per available day.
self.NUM_REMINDERS = BalConfig(config, "bal_num_reminders", 3)
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)
# "Add transaction without will-executor" backup tx. Default OFF
# (False): a fresh wallet does NOT create the extra no-will-executor
# backup transaction (the "azure" tx), so a plain inheritance has no
# backup tx unless the user explicitly enables it from the wizard. The
# chosen value is persisted per wallet, so reopening the plugin always
# follows what is saved in that wallet (the default only applies when no
# value has been stored yet, i.e. new wallets).
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", False)
self.HIDE_REPLACED = BalConfig(config, "bal_hide_replaced", True)
self.HIDE_INVALIDATED = BalConfig(config, "bal_hide_invalidated", True)
self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True)
self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True)
# SIMPLE / ADVANCED mode (global, plugin-wide).
#
# "basic" -> SIMPLE mode (DEFAULT): hides advanced controls (the
# Raw/Date selector and the "Check Alive" field/icon) and
# disables the "check alive" postpone behaviour, so the
# plugin is easier for non-technical users.
# "advanced" -> shows every control (the original behaviour).
#
# This is stored in Electrum's GLOBAL config (not in the wallet file),
# so it never affects compatibility with existing wallets: an old wallet
# simply opens with whatever global value is set, and its owner can
# switch to "advanced" from the plugin settings whenever they want.
self.USER_TYPE = BalConfig(config, "bal_user_type", "basic")
self.WELIST_SERVER = BalConfig(
config, "bal_welist_server", "https://welist.bitcoin-after.life/"
)
@@ -315,6 +335,16 @@ class BalPlugin(BasePlugin):
will_settings["locktime"] = defaults['locktime']
return will_settings
def is_basic_mode(self):
"""Return True when the plugin runs in SIMPLE ("basic") mode.
Centralises the USER_TYPE check so the GUI never compares the raw
string in many places. Anything other than the explicit "advanced"
value is treated as basic, so the safe/simple behaviour is the default
even if the stored value is missing or unexpected.
"""
return str(self.USER_TYPE.get()).lower() != "advanced"
@staticmethod
def default_will_settings():
"""Default will settings: a fee rate plus absolute threshold/locktime."""