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:
@@ -349,6 +349,47 @@ class Will:
|
||||
|
||||
Will.search_anticipate_rec(will, old_inputs)
|
||||
|
||||
@staticmethod
|
||||
def _same_heirs(old_heirs, new_heirs):
|
||||
"""Return True if two heir maps describe the SAME inheritance.
|
||||
|
||||
Used by update_will (Option A) to decide whether a rebuilt transaction
|
||||
that kept the same txid can safely reuse the old (possibly already
|
||||
signed) WillItem, or whether the heirs changed and the item must be
|
||||
rebuilt as unsigned.
|
||||
|
||||
Two heir maps are considered equal when they have exactly the same heir
|
||||
names (keys) and, for each heir, the same destination ADDRESS, the same
|
||||
requested AMOUNT and the same LOCKTIME. Internal will-executor
|
||||
pseudo-heirs (keys starting with the reserved ``w!ll3x3c"`` prefix) are
|
||||
ignored, exactly as in check_willexecutors_and_heirs, because they are
|
||||
bookkeeping entries and not real heirs.
|
||||
|
||||
Args:
|
||||
old_heirs: heirs dict stored in the old (existing) WillItem.
|
||||
new_heirs: heirs dict of the freshly rebuilt WillItem.
|
||||
|
||||
Returns:
|
||||
bool: True if the real heirs are identical, False otherwise.
|
||||
"""
|
||||
def _real_heirs(heirs):
|
||||
# Keep only the real heirs and only the fields that define the
|
||||
# inheritance (address/amount/locktime), so cosmetic or derived
|
||||
# fields can never trigger a spurious "heirs changed" rebuild.
|
||||
out = {}
|
||||
for name, entry in (heirs or {}).items():
|
||||
if str(name)[:9] == 'w!ll3x3c"':
|
||||
continue
|
||||
# Heir entry layout (see heirs.py): [0]=address, [1]=amount,
|
||||
# [2]=locktime. We compare exactly the same fields that
|
||||
# check_willexecutors_and_heirs uses (their[0], their[1],
|
||||
# their[2]); index literals are used here to avoid importing the
|
||||
# heirs module (which would create a circular import).
|
||||
out[name] = (entry[0], entry[1], entry[2])
|
||||
return out
|
||||
|
||||
return _real_heirs(old_heirs) == _real_heirs(new_heirs)
|
||||
|
||||
@staticmethod
|
||||
def update_will(old_will, new_will):
|
||||
all_old_inputs = Will.get_all_inputs(old_will, only_valid=True)
|
||||
@@ -368,9 +409,32 @@ class Will:
|
||||
new_heirs = new_will[oid].heirs
|
||||
new_we = new_will[oid].we
|
||||
|
||||
new_will[oid] = old_will[oid]
|
||||
new_will[oid].heirs = new_heirs
|
||||
new_will[oid].we = new_we
|
||||
# OPTION A (heir-change full rebuild, user-approved):
|
||||
#
|
||||
# Historically, whenever a rebuilt transaction kept the SAME
|
||||
# txid as an old one, we REUSED the old WillItem object (which
|
||||
# may already be signed/COMPLETE/PUSHED) and only copied the new
|
||||
# heirs/will-executor onto it. That silently preserved the
|
||||
# "already signed" status even when the HEIRS had actually
|
||||
# changed (e.g. an heir was deleted, so amounts must be
|
||||
# recomputed and the whole wallet re-swept). The downstream
|
||||
# have_to_sign check then saw the item as COMPLETE and reported
|
||||
# "Nothing to do", so the new will was never signed/broadcast
|
||||
# (bugs E/F/K).
|
||||
#
|
||||
# We now reuse the old item ONLY when the heirs are IDENTICAL.
|
||||
# If the heir set/values changed, we keep the freshly built
|
||||
# item (status "New", not COMPLETE) so it is correctly detected
|
||||
# as needing a new signature and broadcast. The will-executor is
|
||||
# still refreshed in both cases.
|
||||
if Will._same_heirs(old_will[oid].heirs, new_heirs):
|
||||
new_will[oid] = old_will[oid]
|
||||
new_will[oid].heirs = new_heirs
|
||||
new_will[oid].we = new_we
|
||||
else:
|
||||
# Heirs changed: keep the new (unsigned) item but make sure
|
||||
# it carries the up-to-date will-executor.
|
||||
new_will[oid].we = new_we
|
||||
|
||||
continue
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user