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

@@ -417,6 +417,32 @@ class Plugin(BalPlugin):
# be saved on a USB stick and a copy given to the heirs).
heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)
# USER TYPE selector (SIMPLE / ADVANCED, global). A two-choice combo
# (not a free-text field) bound to the USER_TYPE config:
# index 0 -> "BASIC" -> stored value "basic" (DEFAULT)
# index 1 -> "ADVANCED" -> stored value "advanced"
#
# BASIC hides the advanced controls (Raw/Date selector and the
# "Check Alive" field) and disables the check-alive postpone behaviour.
# Changing it refreshes the open windows so the controls appear/disappear
# immediately. It is kept in a named variable so the "Reset" button can
# restore its displayed value after a reset.
user_type_combo = QComboBox()
user_type_combo.addItems([_("BASIC"), _("ADVANCED")])
# Map the stored string to the combo index (anything but "advanced"
# falls back to BASIC, matching BalPlugin.is_basic_mode()).
user_type_combo.setCurrentIndex(
0 if str(self.USER_TYPE.get()).lower() != "advanced" else 1
)
def on_user_type_change(idx):
# Persist "basic"/"advanced" and refresh the open windows so the
# advanced controls appear/disappear right away.
self.USER_TYPE.set("advanced" if idx == 1 else "basic")
self.update_all()
user_type_combo.currentIndexChanged.connect(on_user_type_change)
# Editable line/text widgets are created once and kept in named
# variables so the "Reset" button (Group C / C4b) can refresh the
# displayed values after resetting the underlying config.
@@ -447,6 +473,21 @@ class Plugin(BalPlugin):
# Reset/support button row (bottom). Assigning ``QGridLayout(d)`` would
# have made the grid the dialog's only layout, leaving no room for them.
grid = QGridLayout()
add_widget(
grid,
"User Type",
user_type_combo,
0,
(
"Choose how much detail the plugin shows.\n\n"
"BASIC (default): a simpler interface. It hides the advanced "
"controls (the Raw/Date selector and the 'Check Alive' field) "
"and turns off the 'check alive' postpone behaviour, so you "
"only set the delivery date.\n\n"
"ADVANCED: shows every control, including the 'Check Alive' "
"field and the Raw/Date selector."
),
)
add_widget(
grid,
"Hide Replaced",
@@ -475,7 +516,7 @@ class Plugin(BalPlugin):
)
add_widget(
grid,
"Editable dates",
"Panel editable Date and Fee",
heir_editable_dates,
4,
(
@@ -485,14 +526,38 @@ class Plugin(BalPlugin):
"When disabled, those dates are display-only outside the wizard."
),
)
# "Add transaction without will-executor" setting (formerly labelled
# "No will-executor TX"). When ON the plugin ALSO builds the backup
# inheritance transaction that does NOT require a will-executor (the
# "celeste"/light-blue one shown in the will list): it can be saved on a
# USB stick and a copy handed to the heirs. When OFF only the
# transactions destined to the selected will-executors are built.
#
# Placed here (row 5, right below "Panel editable Date and Fee" and above
# "Number of reminders") at the user's request so related options sit
# together. The remaining grid rows below were renumbered accordingly.
add_widget(
grid,
"Add transaction without willexecutor",
heir_no_willexecutor,
5,
(
"Create a will that does not require a Will-executor; it can be "
"saved, for example, on a USB stick, and a copy can be given to "
"the heirs."
),
)
add_widget(
grid,
"Number of reminders",
heir_num_reminders,
5,
6,
(
"How many reminder alarms the exported calendar (.ics) event "
"contains.\n"
"contains.\n\n"
"BASIC MODE:\n"
"Calendar reminder 30, 10 and 1 days before.\n\n\n"
"ADVANCED MODE:\n"
"The reminders are spread across the check-alive period and "
"always fall before the delivery deadline.\n"
"If the period is shorter than the requested number, at most "
@@ -503,7 +568,7 @@ class Plugin(BalPlugin):
grid,
"Event summary",
edit_event_summary,
6,
7,
(
"Default message to be used in event summary\n"
"Variables:\n"
@@ -516,7 +581,7 @@ class Plugin(BalPlugin):
grid,
"Event description",
edit_event_description,
7,
8,
(
"Default message to be used in event description\n"
"Variables:\n"
@@ -527,21 +592,6 @@ class Plugin(BalPlugin):
)
#add_widget(grid, "Bal Mode", bal_mode, 4, "choose bal mode")
# "No will-executor TX" setting. Mirrors the checkbox shown in the
# wizard's will-executor download window (both bound to NO_WILLEXECUTOR),
# so it can also be toggled from the plugin settings. Default ON.
add_widget(
grid,
"No will-executor TX",
heir_no_willexecutor,
8,
(
"Create a will that does not require a Will-executor; it can be "
"saved, for example, on a USB stick, and a copy can be given to "
"the heirs."
),
)
# add_widget(
# grid,
# "Ping Willexecutors",
@@ -584,6 +634,7 @@ class Plugin(BalPlugin):
# Map each config object to the widget that displays it, so we can
# both reset the stored value and update what the user sees.
resets = [
(self.USER_TYPE, user_type_combo, "user_type"),
(self.HIDE_REPLACED, heir_hide_replaced, "check"),
(self.HIDE_INVALIDATED, heir_hide_invalidated, "check"),
(self.AUTO_SIGN, heir_auto_sign, "check"),
@@ -607,6 +658,11 @@ class Plugin(BalPlugin):
widget.setText(cfg.default)
elif kind == "text":
widget.setPlainText(cfg.default)
elif kind == "user_type":
# Default is "basic" -> combo index 0; "advanced" -> index 1.
widget.setCurrentIndex(
1 if str(cfg.default).lower() == "advanced" else 0
)
# Refresh the open BAL windows so any dependent view (e.g. the
# editable-dates state is not in this list, but hide filters are)
# reflects the reset values.
@@ -638,6 +694,9 @@ class Plugin(BalPlugin):
# Outer layout: warning (top) -> settings grid -> bottom button row.
outer = QVBoxLayout(d)
outer.addWidget(lbl_warning)
# Blank vertical gap below the red warning so it is not glued to the
# first setting row ("User Type"); requested by the user for readability.
outer.addSpacing(12)
outer.addLayout(grid)
outer.addLayout(bottom_row)