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

@@ -554,6 +554,43 @@ class Heirs(dict, Logger):
locktimes[locktime] = {key: value}
else:
locktimes[locktime][key] = value
# ALL-DUST GUARD (owner request, see CHANGELOG / log analysis).
#
# WHY HERE: ``locktimes`` now contains EVERY heir across EVERY locktime,
# with their final resolved amount already computed and dust-marked
# ("DUST: <n>" in HEIR_REAL_AMOUNT) by fixed_percent_lists_amount /
# normalize_perc above. This is the only place where we can reliably
# tell whether *all* heirs are dust, for BOTH fixed and percentage
# heirs and across all dates. ``prepare_transactions`` only sees the
# single lowest locktime, so checking there would wrongly block a will
# whose later locktimes still have valid heirs (false positive).
#
# WHAT: count the REAL heirs (excluding the internal will-executor
# pseudo-heirs, whose names start with the reserved ``w!ll3x3c"``
# marker) and how many of them have a valid, non-dust amount. If there
# are real heirs but NONE of them is payable, the inheritance would pay
# nobody (only the change + the will-executor fee). Previously such an
# "empty" will was still built, signed, checked and listed; we now
# refuse it and raise HeirAmountIsDustException so the GUI can show a
# clear message and stop. A mix of dust + valid heirs keeps building
# normally with the valid ones (unchanged behaviour).
real_heirs = 0
valid_real_heirs = 0
for heirs_at_locktime in locktimes.values():
for name, heir in heirs_at_locktime.items():
if str(name).startswith('w!ll3x3c"'):
continue
real_heirs += 1
if len(heir) > HEIR_REAL_AMOUNT and "DUST" not in str(
heir[HEIR_REAL_AMOUNT]
):
valid_real_heirs += 1
if real_heirs > 0 and valid_real_heirs == 0:
raise HeirAmountIsDustException(
"All heirs' shares are below the dust limit"
)
return locktimes, onlyfixed
def is_perc(self, key):