- #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).
62 KiB
TASK A (proposed, NOT yet started) — Improve the "WILL EXPIRED" message
Origin: observed by the user in the screenshots of 2026-06-23 (expired will -> invalidate + re-sign path). The LOGIC is correct; only the UX/clarity of the message is improved.
3 improvements approved by the user (to implement later, in English, rules R1-R4 + zip-first):
- Translate the raw Unix timestamp (e.g. 1782118800) into a readable date. Example: instead of "Will Expired 9f1b0a75...: 1782118800" show "Will expired (locktime 2026-06-22 11:00 UTC) - too late to anticipate, will invalidate and re-sign".
- Make the message INFORMATIVE and not an ERROR: red looks like an error, but it is a normal flow. Use a warning colour (orange) or add a sentence like "This is expected: the will is past its locktime, switching to invalidate + re-sign."
- Shorten the will hash for readability (e.g. first 8 + last 4 characters).
Technical notes to handle in DISCOVER when starting:
- Find the code point that builds the "Will Expired ... " string (probably in the "Building Will" wizard).
- Check whether the red colour is set there (rich text / stylesheet).
- Follow METHOD: DISCOVER -> PLAN (wait for OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release only after confirmation.
TASK B (proposed, NOT yet started) — Transaction description labels in History
Origin: observed by the user in the History screenshot of 2026-06-23. In Electrum's "History" tab (on-chain transactions) the BAL plugin writes a coloured description in the "Description" column.
Current state:
- The INHERITANCE transaction is labelled "BAL Transaction" (red colour).
- The INVALIDATE transaction has NO description.
Requested changes (approved by the user):
- RENAME the inheritance label: "BAL Transaction" -> "BAL Inheritance transaction".
- CHANGE the inheritance label colour from RED to GREEN.
- ADD a new label for the invalidate transactions: "BAL Invalidate transaction" in ORANGE (today they appear with no description).
Desired final summary:
| Transaction | Desired label | Colour |
|---|---|---|
| Inheritance | BAL Inheritance transaction | GREEN |
| Invalidate | BAL Invalidate transaction | ORANGE |
Technical notes to handle in DISCOVER when starting:
- Find in the code where the "BAL Transaction" label is set (probably wallet.set_label(txid, ...) or similar) and where/how the colour is set.
- Understand how the plugin distinguishes an inheritance tx from an invalidate tx, so the correct label is applied to each. The invalidate tx today gets NO label -> find where it is created/broadcast and add set_label there.
- Check whether the (red) colour is handled by Electrum or by the plugin, and how to set green for the inheritance.
- Follow METHOD: DISCOVER -> PLAN (wait for OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release only after confirmation.
TASK C (proposed, NOT yet started) — "no will-executor" checkbox also in Plugin Settings
Origin: user request of 2026-06-23. In the "Create your WILL" wizard, will-executor download window, there is the checkbox "Add transactions without willexecutor". The user wants the SAME checkbox also in the "Plugin settings" window (same style as the other rows), default ON, with a HelpButton explaining the function.
Help-button text (provided by the user, to be used verbatim): "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."
IMPORTANT FINDING (DISCOVER already done):
- The config ALREADY EXISTS:
self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)inbal/core/plugin_base.py:193(default True = ON). So it must NOT be created. - The wizard checkbox is in
bal/gui/qt/lists.py:916-918: hbox.addWidget(QLabel(_("Add transactions without willexecutor"))) heir_no_willexecutor = BalCheckBox(self.bal_plugin.NO_WILLEXECUTOR) -> uses BalCheckBox bound to the same config. Adding the same checkbox in the settings keeps the two automatically in sync (same BalConfig). - The Settings window is
settings_dialog()inbal/gui/qt/plugin.py:372. It uses a grid with the helperadd_widget(grid, label, widget, row, help_)(defined inbal/gui/qt/common.py:98) which places: QLabel(col0), widget(col1), HelpButton(col2). Current rows go 1..8 (8 = Rebroadcast button). - There is ALREADY COMMENTED-OUT code that did exactly this (plugin.py:382 and
536-542):
# heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)and an add_widget "Backup Transaction" -> it can be re-enabled/adapted. - There is also a "Reset setting" block (on_reset_defaults, plugin.py:560-596) with
a
resets = [...]list: for consistency the new checkbox must be ADDED to that list so Reset returns it to the default (ON).
Draft PLAN (to refine and get approved when starting):
- In
settings_dialog(): createheir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR). - Add a row with
add_widget(grid, "<label>", heir_no_willexecutor, <row>, "<user help text>").- Decide a short label (e.g. "No will-executor" / "Backup will (no will-executor)") -> ASK THE USER which label they prefer in the left column.
- Decide the row: insert before Rebroadcast (row 8), renumbering the following rows if needed.
- Add
(self.NO_WILLEXECUTOR, heir_no_willexecutor, "check")to theresetslist in on_reset_defaults, so "Reset setting" returns it to ON. - Keep the existing style (HelpButton, grid). Default already ON via config.
QUESTION TO ASK BEFORE EXECUTE: which short label to show in the left column of the settings? (the user only provided the help-button text).
- Follow METHOD: DISCOVER(done) -> PLAN (wait for OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release only after confirmation.
TASK D (proposed, NOT yet started) — Rename the wizard button
Origin: user request of 2026-06-23. The big button that opens the wizard shows "Create your will" and the user wants to change it to "Build your will" (they wrote "BUIL YOUR WILL" -> typo for BUILD).
FINDING (DISCOVER already done):
- The button is in
bal/gui/qt/lists.py:473: wizard = QPushButton(" " + _("Create your will")) - EXISTING INCONSISTENCY: the tooltip of the SAME button (lists.py:483) already says wizard.setToolTip(_("Wizard - Build your will")) and all the code comments (8 occurrences) call the wizard "Build your will". So changing the button text to "Build your will" UNIFIES everything.
AGENT OPINION (agreed with the user): "Build your will" is the better choice (consistent with tooltip/comments/config; "Build" better conveys the step-by-step guided process).
FORM CHOSEN BY THE USER: "Build Your Will" (title case).
Draft PLAN: replace only the string "Create your will" -> "Build Your Will"
(title case) in lists.py:473. Check there are no other occurrences of the
button text to align. Leave the tooltip unchanged (already "Build your will").
- Follow METHOD: DISCOVER(done) -> PLAN (wait for OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release only after confirmation.
================================================================
PENDING TASK (post-v0.3.9) — UNIFY INVALIDATE PROCEDURE — NOT STARTED
Status: WAITING for user to provide MORE input before planning/acting.
User said (translated from Italian): "OPTION A ... but wait before acting, I have more to feed you"
then refined the requirement (see below), then: "but wait for more, meanwhile save this point"
================================================================
GOAL: make the "invalidate" procedure IDENTICAL for both CHECK button and WIZARD.
Currently there are TWO different invalidate procedures (user noticed the inconsistency):
- PROCEDURE 1 "classic/manual" = window.py::invalidate_will (line 695): waiting dialog + "please sign and broadcast" popup + CLASSIC Electrum tx window (Sign/Broadcast buttons) + SETS history label "BAL Invalidate transaction" (line 704). Used by: Tools->Invalidate menu (lists.py:468->571), dialog button (dialogs.py:1356), on-close/postpone paths (window.py:539,576,614), and the wizard-add-heir popup (my v0.3.9).
- PROCEDURE 2 "automatic" = dialogs.py::invalidate_task (line 902): password prompt inside wizard -> sign + auto broadcast (loop_broadcast_invalidating, line 729) -> DOES NOT set the history label. Used by CHECK button (lists.py:545 -> BalBuildWillDialog.build_will_task) and the FIRST WillExpiredException handler in task_phase1 (dialogs.py:594 -> return None, Will.invalidate_will at 598).
CHECK button flow: lists.py:545 check() -> BalBuildWillDialog(...).build_will_task()
-> task_phase1 -> on_success_phase1. SAME engine as wizard.
- If expired immediately -> FIRST handler (dialogs.py:594) -> return None, Will.invalidate_will -> on_success_phase1 have_to_sign is None -> password prompt "Invalidate your old will" -> invalidate_task (PROCEDURE 2, NO label).
- If heir added -> HeirNotFoundException -> build_will -> inner check expired -> "invalidate_classic" signal -> my v0.3.9 popup (Tools->Invalidate).
FINAL REQUIREMENT (user, latest):
- CHECK and WIZARD must behave IDENTICALLY.
- First show a WARNING popup (no more "use the top-right menu Tools -> Invalidate" text).
- Then AUTOMATICALLY open the CLASSIC Electrum sign window (PROCEDURE 1 / window.py::invalidate_will), which sets the "BAL Invalidate transaction" label and lets the user Sign + Broadcast.
- So: warning popup -> user clicks OK -> classic sign window opens BY ITSELF, IN FRONT.
APPROACH agreed-in-principle (still need final OK + user has MORE input coming):
- Close the CHECK/wizard dialog FIRST, show warning, then open classic window LAST so it stays in front (the focus problem before came from the wizard closing AFTER the tx window opened).
- Unify: route the expired cases (FIRST handler + invalidate_classic + CHECK) to the SAME helper that shows the warning then calls window.py::invalidate_will (PROCEDURE 1). Drop PROCEDURE 2 (invalidate_task) usage for expired.
- KNOWN RISK: auto-opening the classic window while a dialog closes previously put it BEHIND the wallet on the user's PC. Mitigation: ensure the classic window is the LAST thing opened (nothing closes after it). Tools->Invalidate works perfectly precisely because nothing else is closing.
WARNING POPUP TEXT (verbatim, user-approved, English per R1):
"Your will has expired and must be invalidated before it can be rebuilt. A transaction window will now open: please SIGN and then BROADCAST it to invalidate your old will. After the invalidation is confirmed, press the Check button to finish the will." (NOTE: keep the exact wording incl. double space "and then BROADCAST" as user wrote it? -> ASK / normalize to single space.)
DO NOT ACT YET. Wait for user's additional input. Then: full PLAN -> wait OK (R4) -> zip-first.
================================================================
FUTURE TASK (analysis only, NOT started) — SIMPLE / ADVANCED mode
Status: DISCOVER done. No PLAN yet, no code. User said "intanto fa l'analisi e tieni tutto qui".
================================================================
USER REQUEST
Add a SIMPLE / ADVANCED switch in the plugin settings panel.
- SIMPLE (DEFAULT): hide RAW mode and the CHECK ALIVE parameter from the UI.
- ADVANCED: everything visible as today.
- A wallet that already used RAW / has advanced wills must open in ADVANCED mode.
- MUST keep compatibility with existing wallets that have OLD inheritances.
- User asked whether it is feasible without rewriting the engine.
USER CLARIFICATIONS (verbatim meaning)
- CHECK ALIVE = proof the user is still alive: opening Electrum proves you are alive; if you open the plugin AFTER the check-alive date, it proposes creating a postponed (anticipated/rescheduled) inheritance.
- In SIMPLE mode, locktime entry uses normal DATA mode (calendar), not RAW.
- A wallet that had RAW should open the plugin in ADVANCED mode.
TECHNICAL FINDINGS (DISCOVER)
- CHECK ALIVE =
will_settings["threshold"](a timestamp). Read in window.py::init_class_variables (~459):date_to_check = BalTimestamp(threshold).to_timestamp(); ifdate_to_check < now-> raises CheckAliveError (~469). Default relative dates: threshold "30d", locktime "1y" (plugin_base.py:341-342; defaults built at ~320-337).thresholdis NOT used inside will.py (the tx-core); it lives in will_settings and drives the "are you still alive / postpone" prompt. - DELIVERY TIME =
will_settings["locktime"]= the actual tx locktime. ESSENTIAL, untouched. - RAW / DATA = only an INPUT MODE in the UI (widgets.py): RAW = type "30d"/"1y" (TimeRawEditWidget / LockTimeRawEdit ~378/411); DATA = pick a calendar date. Combo defined ~253 options ["Raw","Date"]. The SAVED value is always a date/timestamp, so RAW vs DATA does NOT change what is stored in the will -> NO compatibility impact.
- ThresholdTimeWidget (widgets.py:559) = the CHECK ALIVE editor (base_field="threshold", label "🚨"). LockTimeWidget (591) = DELIVERY TIME (base_field="locktime", label "🚛").
- Help text for CHECK ALIVE already documents DATA vs RAW behaviour (widgets.py:562-575).
FEASIBILITY VERDICT
- Feasible: YES.
- Rewrite engine from scratch: NOT NEEDED and NOT RECOMMENDED. CHECK ALIVE and RAW sit ON TOP of the engine. SIMPLE mode = UI hiding + sensible defaults, NOT an engine rewrite.
- Compatibility with old wallets: PRESERVED, because the engine keeps reading the same threshold/locktime timestamps.
PROPOSED APPROACH (to be turned into a PLAN later, then wait OK)
- New config flag e.g. BalConfig "bal_ui_mode" / SIMPLE default (or a boolean ADVANCED=False).
- SIMPLE: force locktime editor to DATA mode and HIDE the Raw/Date combo; HIDE the CHECK ALIVE (threshold) field; set threshold to a safe default automatically so init_class_variables still works (DECISION NEEDED: what default? e.g. threshold = locktime, or threshold = locktime minus a small delta, or disable the check-alive prompt entirely in SIMPLE). ASK THE USER what CHECK ALIVE should default to in SIMPLE before coding.
- ADVANCED: current behaviour, all fields visible.
- AUTO-DETECT advanced wallets: if an existing wallet/will was created with RAW or has a non-default threshold, open in ADVANCED automatically (per user rule #3). DECISION NEEDED: exact detection criterion (e.g. threshold != default, or a stored marker).
- Engine (will.py, heirs.py) stays UNCHANGED -> compatibility preserved.
OPEN QUESTIONS TO ASK BEFORE PLAN
- In SIMPLE mode, what should CHECK ALIVE do by default? (a) disabled/no prompt, (b) auto-set to a value (which?), (c) something else.
- Exact rule to auto-detect "advanced" wallets to force ADVANCED mode on open.
- Should the SIMPLE/ADVANCED switch be global (plugin-wide) or per-wallet?
DO NOT ACT. Analysis stored. Wait for user to resume + answer open questions.
SIMPLE/ADVANCED — USER DECISIONS (round 2) — still analysis only, DO NOT code
DECISION 1 — CHECK ALIVE in SIMPLE mode
In SIMPLE mode, CHECK ALIVE must behave AS IF IT DID NOT EXIST as a parameter that influences rewriting the inheritance. I.e. it must NOT trigger the "you are alive -> postpone / rewrite the will" behaviour. So in SIMPLE mode the check-alive prompt is effectively NEUTRALIZED (no postpone proposal driven by threshold). TECHNICAL IMPLICATION: window.py::init_class_variables (~459-470) computes date_to_check from will_settings["threshold"] and raises CheckAliveError if it is in the past. In SIMPLE mode we must avoid that path influencing rewrites -> e.g. set threshold so it never triggers (or skip the check-alive logic entirely when mode==SIMPLE). Exact mechanism to be decided in PLAN, but the INTENT is: SIMPLE = no check-alive effect.
DECISION 2 — persistence of the mode
- NEW wallets/wills: store the mode (SIMPLE or ADVANCED) as a piece of information saved WITH the wallet (per-wallet), via wallet.db (same place as "will": window.py uses self.wallet.db.get_dict("will"); plugin_base.py registers dicts e.g. json_db.register_dict("will_settings", ...) at line 55). So add a stored marker, e.g. in will_settings or a dedicated db key (to be decided in PLAN).
- OLD wallets that do NOT have this info: open by DEFAULT as ADVANCED. (This also satisfies rule #3 from round 1: a wallet that used RAW opens in ADVANCED, because old wallets default to ADVANCED.)
DECISION 3 — the SIMPLE/ADVANCED switch is GLOBAL (plugin-wide)
- The toggle itself lives in the plugin settings panel as a GLOBAL config (a BalConfig, like the others in plugin_base.py ~146-211), default SIMPLE.
- BUT each wallet also remembers the mode it was created/saved with (decision 2). -> PLAN must reconcile: global switch default = SIMPLE, yet an existing/old wallet opens ADVANCED, and new wallets persist their mode. Need to define precedence: likely the per-wallet stored mode wins when present; the global switch sets the default for NEW wallets and the global UI default. CONFIRM precedence with user in PLAN.
PERSISTENCE MECHANISMS CONFIRMED (DISCOVER)
- Per-wallet data: self.wallet.db.get_dict("will") (window.py:133). will_settings dict registered via json_db.register_dict("will_settings", ...) (plugin_base.py:55).
- Global plugin settings: BalConfig wrappers over Electrum config (plugin_base.py:58, 146+).
- => GLOBAL switch = new BalConfig; PER-WALLET mode marker = new key in wallet.db / will_settings. Both feasible, engine untouched.
STILL OPEN FOR PLAN (ask/confirm before coding)
- Precedence rule when global switch and per-wallet stored mode disagree.
- Exact SIMPLE-mode mechanism to neutralize check-alive (set threshold vs skip logic).
- Where exactly to store the per-wallet mode marker (will_settings key name).
- What other ADVANCED-only UI elements to hide in SIMPLE besides RAW combo + CHECK ALIVE (e.g. multiverse, editable dates, num reminders?) -> ASK user for the full SIMPLE list.
DO NOT ACT. Wait for user to resume. Then full PLAN -> wait OK (R4) -> zip-first.
================================================================
NEW TASKS (added by user, latest message) — TO-DO LIST ONLY, NOT STARTED
User: "I'm adding more points for you to put on the to-do list"
NO coding yet. Each needs DISCOVER(refine) -> PLAN -> wait OK (R4) -> zip-first.
================================================================
TASK #01 — Fix misleading "heir not found" message after wizard when date was only anticipated
User (translated from Italian): "after creating an annuity and running the wizard, the plugin says 'heir not found', but in reality the date was only anticipated; the informational message is wrong, but everything else works fine." Problem: the FUNCTIONAL behaviour is correct (the will is rebuilt with the anticipated/postponed date). Only the INFORMATIONAL message is wrong/misleading: it says "Heir not found" when actually the date was simply anticipated. TECHNICAL CONTEXT (already discovered):
- The message comes from dialogs.py::task_phase1, in the
except NotCompleteWillException as e:block (~625). At lines ~641-642: elif isinstance(e, HeirNotFoundException): message = _("Heir not found") - HeirNotFoundException is a subclass of NotCompleteWillException and is raised by will.py::search_rai (check_will order at will.py:561: check_invalidated -> check_will_expired -> search_rai).
- So when an heir is added / date anticipated, the rebuild path raises HeirNotFoundException and the user sees "Heir not found", which is misleading. DISCOVER to refine when starting: confirm in which exact scenario(s) HeirNotFoundException is raised during a normal anticipate/postpone flow; decide the correct, non-alarming wording (English, R1). Possibly distinguish "genuine heir-not-found error" vs "date anticipated, rebuilding" so the message is accurate in both cases. ASK user for preferred wording if ambiguous (R3).
TASK #02 — Will-executor server list: green-check ONLY servers that responded + green ping; re-evaluate each download
User (translated from Italian): "when I download the will-executor list from the wizard, the plugin must green-check only the servers that responded correctly and that have a green ping dot. otherwise the whole plugin gets stuck and keeps broadcasting to the ones that do not respond well. better to discard at the source the servers that do not respond well right away. this must apply every time I download the server list; if on the second download a server that previously did not respond now responds, the plugin adds it to the server list." Goal:
- When downloading the will-executor list from the wizard, auto-select (green check) ONLY servers that (a) responded correctly AND (b) have a GREEN ping dot.
- Discard non-responsive servers AT THE SOURCE (do not select / do not broadcast to them) so the plugin does not get stuck continuously broadcasting to dead/slow servers.
- This must apply EVERY time the list is downloaded. On a later download, if a previously non-responsive server now responds correctly, it gets added/selected again. TECHNICAL CONTEXT (already discovered, to refine in DISCOVER):
- Download/selection logic: bal/core/willexecutors.py (get_willexecutors, get_willexecutor_transactions, is_selected, push logic). Willexecutors.is_selected(...) decides the green checkmark.
- Wizard download window/checkbox UI is in lists.py (download list ~917 area).
- The push/broadcast loop is dialogs.py::loop_push (~750), which contacts SELECTED servers.
- NEED: filter out servers that don't respond / lack green ping BEFORE selecting them, and re-run this evaluation on every download (so the selected set reflects current health each time). DISCOVER to refine when starting: find exactly where the ping/green-dot status is computed and where is_selected is set after a download; decide the criterion ("responded correctly" + "green ping") and how to re-evaluate on each download without losing manual user choices (ASK if conflict). Engine of will.py untouched.
TASK #03 — Invalidate tx missing "BAL Invalidate transaction" history label when invalidate done from the AUTO-opened window
User (translated from Italian): "as already discussed with you before, the invalidate transaction did not write 'BAL invalidate transaction' in the wallet history if the invalidate was done from the window that opens automatically; whereas it only writes it when I invalidate from the TOOLS menu, Invalidate manually." Problem: the history label "BAL Invalidate transaction" is written ONLY when invalidating manually via Tools -> Invalidate (PROCEDURE 1), NOT when invalidating from the window that opens automatically (PROCEDURE 2). TECHNICAL CONTEXT (already discovered):
- PROCEDURE 1 (manual, GOOD): window.py::invalidate_will (~695) sets the label at ~704: self.wallet.set_label(result.txid(), "BAL Invalidate transaction")
- PROCEDURE 2 (automatic, MISSING label): dialogs.py::invalidate_task (~902) + loop_broadcast_invalidating (~729). Neither calls set_label. The FIRST WillExpiredException handler in task_phase1 (dialogs.py ~594-607 -> return None, Will.invalidate_will) routes the CHECK button to PROCEDURE 2, which is why the label is missing. OVERLAP: this is the SAME root cause described in the saved "PENDING TASK — UNIFY INVALIDATE PROCEDURE" above. Fixing the unify task (route all expired-invalidate cases to PROCEDURE 1, which sets the label) would also fix TASK #03. Keep them linked: solving "unify invalidate" with auto-open of the classic window (PROCEDURE 1) resolves #03 automatically. If implemented separately, the minimal fix is to add set_label("BAL Invalidate transaction") in the PROCEDURE 2 broadcast path.
TASK #04 — Raw/Date selector combo not shown on the WILL/HEIR tabs (only inside the wizard)
User (translated from Italian): "when I select Advanced in the plugin settings, the Raw mode of the locktime and check-alive boxes only appears inside the wizard, and no longer on the Will and Heir tabs, even if I tick the 'panel editable fee and date' setting. Inside the wizard it works perfectly; outside the wizard only the DATE works." Plus: "if I set Raw inside the wizard for check-alive/locktime, then on the Will/Heir tabs the raw number appears, but NOT the box to choose Raw or Date." ANALYSIS (already done, confirmed by reading the code):
- Each date/locktime field is
BalTimeEditWidget(widgets.py). Its__init__readsis_basic_mode()ONCE (line ~293) and, in BASIC, hides the Raw/Date combo (line ~336self.combo.setVisible(False)). - The WILL/HEIR tab toolbars are created ONCE and REUSED for the whole session (not rebuilt on USER TYPE change), so the combo stays in its initial (hidden) state.
apply_user_type_visibility()(widgets.py ~888), called fromBalWindow.update_all()(window.py ~1343) on a BASIC<->ADVANCED switch, ONLY toggles the Check-Alive ROW visibility (threshold.setVisible(not basic), line ~919). It NEVER touchesself.combo.setVisible(...)nor the editor.- Raw value showing but combo hidden: when the wizard saves a raw value,
update_setting_widgets->update_widget_value->set_value(raw)writes the value into the active editor (so the raw NUMBER shows), andupdate_combo_setting_widgets->set_indexsets the combo INDEX to Raw "behind the scenes" — but the combo is stillsetVisible(False), so the selector box stays invisible. - The wizard works only because it is RECREATED on every open (re-reads the current mode).
ROOT CAUSE (unified): the Raw/Date combo on the toolbars is hidden permanently at
__init__and nothing ever makes it visible again, neither on ADVANCED switch nor when a raw value arrives. PROPOSED FIX (to detail in PLAN): add a method toBalTimeEditWidget(e.g.apply_user_type_visibility()) that re-readsis_basic_mode()and doesself.combo.setVisible(not basic)WITHOUT changing the current value or editor (per user req #1: keep current value to avoid confusion between date and inheritance). Call it fromWillSettingsWidget.apply_user_type_visibility()for BOTHlocktimeANDthreshold(per user req #2: applies to both boxes). So toggling BASIC<->ADVANCED, and a raw value pushed from the wizard, both correctly reveal the combo on the already-existing WILL/HEIR toolbars without restarting Electrum. USER CONFIRMATIONS: (1) on ADVANCED, show the combo but KEEP the current value/editor (do not reset) to avoid confusing date vs inheritance; (2) applies to BOTH locktime AND check-alive boxes.
TASK #04 STATUS: DONE in v0.4.8 (user approved, implemented this session).
Fix: added BalTimeEditWidget.apply_user_type_visibility() (shows/hides the Raw/Date
combo, keeps current value/editor) and wired it into WillSettingsWidget.apply_user_type_visibility()
for BOTH locktime and threshold. Delivered together with the other v0.4.8 UX changes.
DO NOT ACT on #01/#02/#03. Saved to to-do list only. Wait for user to add more or to choose one;
then DISCOVER -> PLAN -> wait OK (R4) -> zip-first.
PLUGIN STATES TABLE (requested in TASK #01 — for .md documentation)
Source of truth: bal/core/will.py STATUS_DEFAULT (lines 898-917) + side-effect rules in
set_status (lines 919-970); "Server" column derived in bal/gui/qt/theme.py server_status_text
(lines 63-83). Built by READING the code (R3 — not invented). The visible "Stato" column is a
COMPOSITE string built by appending each flag as it is set: status += "." + name, with "NOT "
prepended when a flag is cleared (will.py:952). That is why screenshots show chains like
"New.Firmato.Pushed.Checked.Confirmed".
A. Individual status flags (STATUS_DEFAULT) — 18 flags
Each flag is [label, default_value]. Default value is the initial boolean.
| Key (code) | Label shown | Default | Meaning (WHY it exists) |
|---|---|---|---|
| ANTICIPATED | Anticipated | False | Locktime moved earlier by 1 day. KEEPS VALID on purpose (still deliverable). |
| BROADCASTED | Broadcasted | False | The transaction was broadcast to the Bitcoin network. |
| CHECKED | Checked | False | Will-executor server confirmed it holds the tx. Setting it also sets PUSHED and clears PUSH_FAIL. |
| CHECK_FAIL | Check Failed | False | The post-push verification on the server failed. |
| COMPLETE | Signed | False | The will transaction has been fully signed. |
| CONFIRMED | Confirmed | False | Tx confirmed on-chain. Clears VALID and clears INVALIDATED. |
| ERROR | Error | False | A generic error state for the item. |
| EXPIRED | Expired | False | The will is past its locktime (delivery time reached). |
| EXPORTED | Exported | False | The will item was exported (backup/share). |
| IMPORTED | Imported | False | The will item was imported from external data. |
| INVALIDATED | Invalidated | False | A spend invalidated this will. Clears VALID. Cleared again if CONFIRMED/MEMPOOL. |
| MEMPOOL | Mempool | False | Tx seen in the mempool. Clears VALID and clears INVALIDATED. |
| PUSH_FAIL | Push failed | False | Sending the tx to the will-executor server failed. |
| PUSHED | Pushed | False | Tx was sent (pushed) to the will-executor server. Setting it clears PUSH_FAIL and CHECK_FAIL. |
| REPLACED | Replaced | False | Superseded by another will tx. Clears VALID. |
| RESTORED | Restored | False | Item restored (e.g. from a backup). |
| UPDATED | Updated | False | Replaced by a new tx with SAME locktime + SAME heirs. KEEPS VALID on purpose. |
| VALID | Valid | True | The only flag that starts True. The will is currently a valid, deliverable tx. |
B. Side-effect rules (state machine) — will.py set_status (lines 955-969)
- INVALIDATED, REPLACED, CONFIRMED, MEMPOOL -> clear VALID.
- CONFIRMED, MEMPOOL -> also clear INVALIDATED (tx is on-chain/in mempool).
- PUSHED -> clear PUSH_FAIL and CHECK_FAIL.
- CHECKED -> set PUSHED, clear PUSH_FAIL.
- ANTICIPATED and UPDATED -> intentionally KEEP VALID (not in the clear list).
C. "Server" column (online will-executor state) — theme.py server_status_text, priority order
Evaluated top-to-bottom; first match wins.
| Condition (flags) | Text shown |
|---|---|
| CHECK_FAIL AND NOT CHECKED | Not on server |
| CHECKED | Confirmed on server |
| PUSH_FAIL | Send failed |
| PUSHED | Sent (not checked) |
| COMPLETE | Signed (not sent) |
| (none of the above) | Not sent |
D. Example composite "Stato" chains (from screenshots) and how they form
- "New" -> fresh item, only VALID true (label may show "New").
- "New.Firmato" -> + COMPLETE (Signed; "Firmato" = old/IT locale).
- "New.Firmato.Pushed" -> + PUSHED.
- "New.Firmato.Pushed.Checked" -> + CHECKED (also implies PUSHED).
- "New.Firmato.Pushed.Checked.Confirmed" -> + CONFIRMED (clears VALID under the hood).
- "New.Firmato.Pushed.Check Failed.NOT Pushed" -> CHECK_FAIL then a later clear of PUSHED -> "NOT Pushed". NOTE: the composite string is APPEND-ONLY history of flag changes, so it can contain both a flag and its later "NOT " negation. The boolean truth is in STATUS[...][1], not in the visible chain.
NOTE for #01 fix: the misleading "Heir not found" message is a SEPARATE UI string in
dialogs.py task_phase1 (~641-642), NOT a status flag. The states table above documents the engine flags; #01 is about correcting the human message when the real cause is a date anticipation.
CHECK-WINDOW MESSAGES LIST (requested by user — outcomes shown in the info window after CHECK)
Source of truth: bal/gui/qt/dialogs.py BalBuildWillDialog — task_phase1 (~542-707), the message
helpers (msg_set_checking/building/signing/pushing/invalidating ~1204-1230), and the color/result
helpers msg_ok/msg_warning/msg_error (~1234-1245). Colors: OK=green #05ad05, WARNING=orange #cfa808,
ERROR=red #ff0000. Built by READING the code (R3 — not invented).
NOTE: this is DIFFERENT from the "PLUGIN STATES TABLE" above. The states table lists the will-item flags (the "Stato" column). THIS list is the human-readable progress/outcome lines printed in the "Building Will" info window when the user presses CHECK (or runs the wizard). The window has fixed ROW LABELS and, for each, a variable STATUS text.
Row labels (left side, fixed) in the CHECK / build window
- "Checking variables"
- "Checking your will"
- "Building your will"
- "Invalidating old will" (only when an invalidation is triggered)
- "Signing your will" (phase 2)
- "Broadcasting your will to executors" (phase 2)
ROW 1 — "Checking variables" outcomes
| Status text shown | Color | When (code) |
|---|---|---|
| Check Alive Threshold Passed: you have to Invalidate your old Will | RED | CheckAliveError + an invalidate tx exists (~559) |
| No Heirs | RED | NoHeirsException (~564) |
| Ok | GREEN | check_amounts passed (~579) |
| In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts | (warning popup) | AmountException (~583) |
ROW 2 — "Checking your will" outcomes (this is where "Heir not found" / "New" appear)
| Status text shown | Color | When (code) | Triggers rebuild? |
|---|---|---|---|
| Ok | GREEN | check_will passed, nothing to change (~593) | no |
| Expired | (plain) | WillExpiredException (~596) -> invalidate path | invalidate |
| Postponed: invalidating old will | (plain) | WillPostponedException (~609) -> invalidate path | invalidate |
| No Heirs | (plain) | NoHeirsException (~614) | no |
| Heirs changed: | (plain) | HeirChangeException (~621) | YES |
| Will-Executor not present | (plain) | WillExecutorNotPresent (~623) | YES |
| Will-Executor changed | (plain) | WillexecutorChangeException (~625) | YES |
| Txfees are changed | (plain) | TxFeesChangedException (~627) | YES |
| Heir not found | (plain) | HeirNotFoundException (~629) [#01: MISLEADING — often it is just an anticipated date] | YES |
| New | (plain) | NotCompleteWillException with no specific subtype (~634) [a brand-new will item, "New heir" case] | YES |
ROW 3 — "Building your will" outcomes
| Status text shown | Color | When (code) |
|---|---|---|
| Ok | GREEN | build_will succeeded (~652) |
| Balance is too low, or CheckAlive is in the past.Skipped | RED | build_will returned nothing (~643) |
| Will-Executor excluded -> Skipped | RED | WillExecutorNotPresent during build (~656) |
| ORANGE | WillExpiredException after rebuild -> returns "invalidate_classic" (~683) | |
| RED | any other Exception during build (~688) | |
| , is DUST -> Excluded from will | ORANGE | a heir's amount is DUST (~695-699) |
ROW 4 — "Invalidating old will" outcomes (loop_broadcast_invalidating ~729)
| Status text shown | Color | When |
|---|---|---|
| Broadcasting | plain | start of broadcast |
| Ok | GREEN | broadcast succeeded |
| RED | TxBroadcastError / BestEffortRequestFailed |
ROW 5 — "Signing your will" (phase 2) — set via msg_set_signing (~1220)
Shows progress while signing; final result via msg_ok / msg_error.
ROW 6 — "Broadcasting your will to executors" (phase 2, loop_push ~750)
| Status text shown | Color | When |
|---|---|---|
| Broadcasting N/M (Xs / Ys) | plain | live progress per selected will-executor |
| : Ok | GREEN | that server accepted the tx (-> PUSHED) |
| : Ko | RED | that server rejected (-> PUSH_FAIL) |
| : Timeout - no answer | RED | server did not answer in time (-> PUSH_FAIL) [related to #02] |
| checking - : Waiting | plain | verifying an "already present" server |
| checked - : <True/False> | GREEN/RED | post-push verification result (-> CHECKED) |
LINK TO #01: the "Heir not found" line (ROW 2) is exactly the misleading message. The fix is to
distinguish the real cause: if the will item only had its date anticipated (locktime moved earlier), show a correct message instead of "Heir not found". DISCOVER will confirm the precise condition.
TASK #01b — Replace misleading "Heir not found" / "New" messages (NOT STARTED — analysis only)
User decision (Option 2): change BOTH the CHECK window (dialogs.py) AND window.py for consistency. User said (translated from Italian): "just put everything on the list, do not make changes for now".
GOAL
Replace the two MISLEADING outcome texts shown in ROW 2 "Checking your will" with a single clear text: NEW TEXT (verbatim, FINAL/approved — TWO LINES):
Found CHANGES to the DATE or the HEIRS,
a NEW WILL must be prepared.
In code this is a single string with a newline: "Found CHANGES to the DATE or the HEIRS,\na NEW WILL must be prepared."
(No trailing quote character; the message ends at "prepared." Note: "CHANGES" is UPPERCASE.)
SCOPE — exactly 3 string changes (Opzione 2: A + B for consistency)
| # | File:line | Current text | Flow | Action |
|---|---|---|---|---|
| 1 | bal/gui/qt/dialogs.py:631 | _("Heir not found") |
CHECK window / wizard (task_phase1) | -> new text |
| 2 | bal/gui/qt/dialogs.py:636 | "New" (msg_set_checking) |
CHECK window / wizard (task_phase1) | -> new text |
| 3 | bal/gui/qt/window.py:594 | "Heir not found" |
build_inheritance_transaction (popup via show_message) | -> new text |
IMPORTANT — what to LEAVE UNCHANGED (do NOT touch)
In the SAME NotCompleteWillException block there are 4 OTHER specific messages that stay AS-IS, because they are still correct/useful:
- "Heirs changed:" (HeirChangeException)
- "Will-Executor not present" (WillExecutorNotPresent) [window.py uses "Will-Executor not present:"]
- "Will-Executor changed" (WillexecutorChangeException)
- "Txfees are changed" (TxFeesChangedException) ONLY the HeirNotFoundException branch and the no-subtype/"New" fallback are replaced.
TECHNICAL NOTES (DISCOVER done)
- dialogs.py block: lines 618-636, inside task_phase1,
except NotCompleteWillException as e:.- line 631:
elif isinstance(e, HeirNotFoundException): message = _("Heir not found") - line 635-636:
else: self.msg_set_checking("New")(the no-specific-subtype fallback = "New").
- line 631:
- window.py block: lines 578-599, inside build_inheritance_transaction, same exception ladder.
- line 594:
elif isinstance(e, HeirNotFoundException): message = "Heir not found" - NOTE: window.py has NO "New" fallback (if message stays False, it shows nothing) -> only the HeirNotFoundException line is changed here. The other branches stay.
- line 594:
- Both blocks then call build/rebuild; behaviour (the rebuild) is UNCHANGED — only the human text changes.
- "New" string is also the basis for the composite "Stato" column? NO — that "New" is a separate UI label; this task only touches the CHECK-window message fallback, not the will-item status flags.
OPEN QUESTION to confirm at PLAN time (before coding)
- dialogs.py:636 currently passes the bare string
"New"(NOT wrapped in_()), so it is not translatable. When replacing, wrap the new text in_( ... )for both files for consistency? (default: YES.)
METHOD when starting (R4): DISCOVER (done) -> PLAN (wait OK) -> EXECUTE -> ruff + full tests ->
ZIP for user to test -> commit ONLY after explicit confirmation. Add a numbered CHANGELOG entry.
TASK BATCH #17 - v0.4.0 USER-TEST FEEDBACK (A-K) - DISCOVER done, PLAN pending OK
User tested ZIP v0.4.0 and reported 11 points. R4: analyze + propose, DO NOT code yet.
UI text / label fixes (low risk)
- (A) widgets.py LockTimeWidget.help_text (~607): insert a line "(ONLY IN ADVANCED MODE)
" right BEFORE the "if you choose Raw, you can insert various options based on suffix:" line. - (B) plugin.py: red warning QLabel (lbl_warning ~465, shown at top via outer.addWidget ~687). Add a blank vertical space below it, above the grid (User Type row). Use addSpacing.
- (C) plugin.py:478 add_widget label "USER TYPE" -> "User Type".
- (D) plugin.py:519 add_widget label "Editable dates" -> "Panel editable Date and Fee". Verify it fits before the checkbox (label is column 0 of the grid; should be fine).
- (G) plugin.py: move the "No will-executor TX" checkbox (currently row 8, ~576) to between "Editable dates"(row4) and "Number of reminders"(row5). Requires renumbering rows 5..8 (+1).
Layout (medium risk)
- (H) widgets.py WillSettingsWidget vertical layout (~674-722, the WIZARD). Requirements: calendar/date box narrower; left-aligned & tidy; fee box width fit to ~5 chars; ADVANCED mode must stay aligned when the check-alive (threshold) row also appears.
Wait-time (low risk)
- (J) Reduce 30s waits to 20s. Locations found: willexecutors.py:46 PUSH_GLOBAL_DEADLINE = 30 -> 20 willexecutors.py:56 CHECK_GLOBAL_DEADLINE = 30 -> 20 (window.py:1226 ping_deadline derives from PUSH_GLOBAL_DEADLINE; window.py:1166 download_deadline=45 is a separate "45s" value - user said only the 30s ones; leave 45 unless asked.) Also comments at willexecutors.py:35,50,294 mention "30s sleeps" (text only, no behaviour).
BUGS (high risk - need careful logic work)
- (E) "eredus" shown in RED on "Building your will:" line.
ROOT CAUSE: dialogs.py:734-735
except Exception as e: self.msg_set_building(self.msg_error(e)). build_will() (window.py:309) raises an exception whose str(e) is the heir name ("eredus"), shown as red error. Likely HeirNotFoundException or similar raised inside get_transactions. QUESTION FOR USER ALREADY ASKED: with 10 heirs does it list all 10 here? -> Answer: the "Building your will:" line shows ONE status only; the per-heir lines that CAN appear are the DUST-exclusion warnings (dialogs.py:739-748), one line per heir that is DUST. So normally NOT all 10 are listed; only DUST heirs get an extra line. Need to confirm which exception carries the heir name to decide the fix (turn into green Ok, or a clear message instead of raw name). - (F) After DELETING one of two heirs: window says "Found CHANGES... a NEW WILL must be prepared" (good) but then "Signing: Nothing to do" / "Broadcasting: Nothing to do", and on exit the WILL tab gets the inheritance tx list but they are NOT signed. Pressing CHECK re-opens same window, still "Nothing to do". RELEVANT CODE: dialogs.py have_to_sign loop (750-754): have_to_sign=True only if some valid willitem is NOT COMPLETE. on_success_phase1 (1077): else branch -> msg_set_signing("Nothing to do") when have_to_sign is False. task_phase2 only signs if have_to_sign. SUSPECT: will.py update_will (366-375): when a txid is unchanged between old/new will, it REUSES the OLD willitem object (which is COMPLETE), so freshly-rebuilt items inherit COMPLETE status and have_to_sign stays False. After deleting an heir the remaining tx may keep same txid -> reused as COMPLETE -> "Nothing to do". NEEDS deeper confirm; possibly the rebuilt tx should be re-flagged "New"/not-COMPLETE when heirs set changed. HIGH RISK - confirm with logs before edit.
- (K) BASIC, add heir from WIZARD keeping SAME date: it correctly rebuilds + signs + creates new tx
in WILL list, but does NOT auto-broadcast. User asks if it should.
RELEVANT CODE: task_phase2 (1142-1153): have_to_push True only if willitem has
.we(a will-executor) AND COMPLETE AND not PUSHED. If no will-executor is attached (e.g. "No will-executor TX" mode or none selected), have_to_push stays False -> "Nothing to do" (correct, nothing to broadcast). Need to confirm user's wallet had a will-executor selected. If yes, the push path should fire; if it didn't, investigate selection state after wizard add-heir. DESIGN QUESTION FOR USER: in BASIC, when AUTO_SIGN is on and a will-executor is selected, should broadcast always be automatic? (current logic only pushes when a will-executor exists.)
Logic verification (no code, just run)
- (I) Run full test suite to confirm BASIC mode (only DELIVERY TIME, no check-alive) works.
PROPOSED GROUPING (for credit efficiency, one ZIP cycle)
- Group A = pure text/label/spacing/move: A, B, C, D, G (very low risk)
- Group B = layout H (calendar narrower, left-align, fee ~5 chars, ADVANCED alignment)
- Group C = wait-time J (30->20 in willexecutors.py x2)
- Group D = bug E (red heir name -> green/clear message)
- Group E = bugs F and K (sign/broadcast logic) - HIGHEST RISK, may need user logs
- Then: (I) run full tests, ruff, build ONE ZIP v0.4.1, user tests, commit only after OK.
TASK BATCH #17 - UPDATE after LOG analysis (log.txt 183 lines, 2026-06-24)
ROOT CAUSE CONFIRMED for E + F (they are the SAME bug)
Log sequence on CHECK after heir change:
- check_willexecutors_and_heirs -> "heir: erede002new not found" -> HeirNotFoundException -> "not complete erede002new true" -> message "Found CHANGES..." -> have_to_build=True. OK
- build_will() -> "txs built: {3 tx}" (new tx created). OK
- dialogs.py:697 self.bal_window.check_will() -> is_will_valid -> check_willexecutors_and_heirs AGAIN -> "heir: erede001 not found" -> raises HeirNotFoundException("erede001").
- That exception is caught by the GENERIC
except Exception as e:(dialogs.py:734) -> msg_set_building(self.msg_error(e)) => shows the HEIR NAME in RED == BUG E thenreturn False, None=> have_to_sign=False => "Nothing to do" / no sign / no push == BUG F Log proof: lines 96-98 "check willexecutors heirs / heir: erede001 not found / have to sign False".
WHY does check_will() still see the OLD heir after rebuild? -> will.py update_will (366-375): for txid unchanged between old/new will it REUSES the OLD WillItem object (old heirs/old we/COMPLETE). So after build_will the willitems still carry stale heir entries -> check_willexecutors_and_heirs raises HeirNotFoundException again. -> Confirmed by user: deleting/adding an heir must FULLY rebuild (values recomputed: single heir auto-scaled to 100%, whole wallet always emptied). So reusing old items is wrong here.
USER WORKAROUND that worked: deleting ALL tx in WILL list, then CHECK -> correctly rebuilds & asks to sign. This confirms: the stale reused items are the problem; with an empty will there is nothing stale to reuse.
FIX DIRECTION (E+F) - to confirm at PLAN:
- The post-build verification at dialogs.py:697 should NOT re-raise HeirNotFound for the freshly rebuilt will. Options: (a) After build_will(), the new willitems must reflect the NEW heirs (not reuse old COMPLETE items whose heirs no longer match). i.e. update_will should NOT copy old heirs onto a tx whose heir SET changed; only reuse when heirs are identical. (b) Or: in dialogs.py wrap/handle the second check_will() so a HeirNotFound on the just-rebuilt will is treated as "needs signing" (have_to_sign=True) instead of red error + return False.
- Preferred: (a) at the source (update_will) so status is correct (new tx => not COMPLETE => have_to_sign True => sign => push). HIGH RISK: update_will is shared by many paths; must run full test suite. Need to be 100% sure before editing (R3).
E - user clarified: show ALL heirs in GREEN.
Today the "Building your will:" line shows ONE status. To list all heirs in green we add, after a successful build, one green line per heir (name) - source: willitems[wid].heirs keys (skip the internal 'w!ll3x3c"' executor pseudo-heirs). Place where msg_set_building(msg_ok()) succeeds (dialogs.py:701). Need a per-heir green row helper (msg_set_status with COLOR_OK).
K - user clarified:
- "No will-executor TX" = the CELESTE/backup tx (the one NOT needing a will-executor). Checkbox ON => also create that backup tx; OFF => only create tx that go to will-executors.
- RENAME setting label to "Add transaction without willexecutor"; if it doesn't fit before the checkbox, use "Add TX without willexecutor". (plugin.py row, currently "No will-executor TX".)
- K bug: after the E+F fix the rebuilt tx will be not-COMPLETE => sign => push. User states 2 will-executors were selected and tx already have we associated, so once have_to_sign becomes True the existing task_phase2 push path (have_to_push when w.we & COMPLETE & not PUSHED) should fire automatically. So K is very likely RESOLVED by the same E+F fix. CONFIRM after fix with a test that simulates add-heir + selected will-executors -> loop_push called.
- User decision: "when coming from the WIZARD, broadcast must ALWAYS be automatic for selected servers". Current logic already auto-pushes when have_to_push; the missing piece was have_to_sign being wrongly False. Keep auto-push as is.
J - user UPDATE: also reduce download_deadline (window.py:1166, =45) to 20, and unify ALL into a
SINGLE variable if possible. PLAN: define one constant (e.g. willexecutors.NETWORK_DEADLINE = 20)
and use it for PUSH_GLOBAL_DEADLINE, CHECK_GLOBAL_DEADLINE, ping_deadline, download_deadline.
REVISED GROUPING (one ZIP v0.4.1):
G1 text/labels/spacing/move: A, B, C, D(+ rename K-label), G (low risk) G2 layout H G3 wait-time J (single shared deadline constant = 20) G4 BUG E+F+K core fix (update_will heir-set reuse OR post-build handling) + show all heirs green Then I (full tests) + ruff -> ZIP v0.4.1 -> user test -> commit after OK.
TASK BATCH #17 - DONE (v0.4.1, delivered as test ZIP, NOT committed)
Implemented G1(A,B,C,D,G + rename K-label) + G2(H layout) + G3(J unified NETWORK_DEADLINE=20)
- G4(E+F+K core fix via Will._same_heirs Option A + all heirs green).
- New helper Will._same_heirs (will.py ~352); update_will reuses old item only if heirs identical.
- dialogs.py build-success: lists every heir in green (COLOR_OK), skips w!ll3x3c" pseudo-heirs.
- widgets.py: help_text "(ONLY IN ADVANCED MODE)"; compact left-aligned wizard date/fee layout (date rows fixed 16-char + icon, fee field 5-char, calendar left-aligned with stretch).
- plugin.py: "User Type", "Panel editable Date and Fee", checkbox moved to row5 & renamed "Add transaction without willexecutor", addSpacing(12) under red warning; rows renumbered.
- willexecutors.py: NETWORK_DEADLINE=20; PUSH/CHECK_GLOBAL_DEADLINE derive from it; class attr added.
- window.py: download_deadline = Willexecutors.NETWORK_DEADLINE.
- Version 0.4.0->0.4.1 (4 files). CHANGELOG entry #17.
- New tests tests/test_group_f_heir_change_rebuild.py (9). Full suite: 248 passed. ruff: no new errors.
- ZIP: bal-electrum-plugin-v0.4.1.zip, 37 files, 263683 bytes, sha256 914954306e8e88c1ecad7be1d459cb9617a6b36fa9bf91fe2d1d8912a22621b2, url https://www.genspark.ai/api/files/s/GLFdDxWj. NOT committed (zip-first; awaiting user OK).
TASK BATCH #18 - v0.4.2 (real E/F/K fix + layout H + tooltip)
CRITICAL CORRECTION: v0.4.1's _same_heirs/update_will fix did NOT fix E/F/K.
Confirmed via electrum_log_20260624T152516Z_24316.log: rebuilt txids
(117c9f/05753d/fbb49f) are ALL NEW vs old (129ef8), so update_will's "reuse old
item if txid matches" branch never runs.
REAL ROOT CAUSE: in dialogs.py task_phase1, after build_will() the second
check_will() raises HeirNotFoundException (subclass of NotCompleteWillException),
caught by generic except Exception -> heir name RED + return False,None ->
"Nothing to do".
REAL FIX (v0.4.2): added except NotCompleteWillException as e: BEFORE generic
Exception (and after WillExecutorNotPresent/WillExpiredException). It calls
_build_success_report() (green Ok + heir list) and falls through to have_to_sign
loop -> sign -> auto-push. Extracted _build_success_report() helper used on both
clean and rebuilt paths.
Layout H (widgets.py): kept composites INTACT (they hold 2 editors + Raw/Date combo in ADVANCED), forced all leading icons to same fixed width + left-align stacking so icons align and fields start at same x; widened fee field to 8 chars.
Tooltip: "Delivery Time, click for more information".
STATUS: 248 tests pass, ruff clean (no new errors), version bumped to 0.4.2, CHANGELOG entry #18 added (and #17 outcome corrected to PARTIAL). ZIP v0.4.2 built (sha256 8e40d6c3...). Delivered for testing. NO COMMIT until user confirms.
TASK BATCH #19 - v0.4.3 (date-sync after auto-anticipate + BASIC calendar + sign reason)
BUG (owner, after v0.4.2 test): after a rebuild the engine auto-anticipates tx locktime by 1 day (Will.check_anticipate), but WILL_SETTINGS["locktime"] stayed at the original date -> next Check compared stored(orig) vs tx(orig-1d) and mistook it for a POSTPONE -> wrongly asked to invalidate.
FIX 1 (dialogs.py): _sync_locktime_to_built_txs() in _build_success_report sets stored locktime = Will.get_min_locktime(willitems) (min of valid built txs), ONLY if min < current (anticipate only, never overwrite a postpone). Routed via BalWindow.update_setting_widgets(update_all=True) so it persists AND refreshes the date widgets in all panels/wizard; the .ics calendar reads the same alarm so it gets the anticipated date too. Owner confirmed: min is fine when multiple txs.
FIX 2 (dialogs.py): _date_was_anticipated flag -> before the sign prompt show an orange note explaining WHY (date moved 1 day earlier so the new will replaces the old one; please sign+broadcast).
FIX 3 (widgets.py): BASIC calendar. check-alive is hidden in BASIC so spreading reminders over it is wrong. New pure helper basic_reminder_offsets(days) + BASIC_REMINDER_OFFSETS=(30,10,1): fixed reminders 30/10/1 days before delivery, dropping any in the past. ADVANCED unchanged. The .ics now uses the (anticipated) locktime alarm. New tests test_group_g_basic_calendar.py (7).
STATUS: 255 tests pass, ruff clean (no new errors), version -> 0.4.3, CHANGELOG #19 added. ZIP v0.4.3 to be built and delivered. NO COMMIT until user confirms.
TASK BATCH #20 - v0.4.4 (UI wording + check-alive visibility + backup-tx default OFF)
P1 (plugin.py:556): reminders help text -> BASIC (30/10/1 days before) + ADVANCED (spread, range 1-5 default 3). P2 (widgets.py wizard else-branch only): QLabel above date "Enter the date on which you want the inheritance (or backup)..."; QLabel below fee "Please note: Do not reduce the miner fees unless you know what you're doing". Wizard only. P3 (lists.py:495, PreviewList=WILL tab): refresh tooltip "Check" -> "Check Inheritance". P4 BUG FIX: check-alive (threshold) visibility was set only in WillSettingsWidget.init; WILL/HEIR toolbars persist across USER TYPE change so ADVANCED didn't re-show it (only wizard, which is recreated). Added apply_user_type_visibility() (shows threshold when not basic) called from window.update_all() for both heir_list_widget & will_list_widget. P5 (plugin_base.py:193): NO_WILLEXECUTOR default True -> False. Confirmed Option A (stored in Electrum GLOBAL config via config.set_key, NOT per-wallet). Default applies only to new installs; saved choice is respected. Reset uses cfg.default so it's OFF too.
STATUS: 255 tests pass, ruff clean (no new errors), version -> 0.4.4, CHANGELOG #20 added. ZIP v0.4.4 to be built/delivered. NO COMMIT until user confirms.
TASK BATCH #21 — v0.4.5 (invalidate loop + wizard truncation)
- Issue 1: wizard QLabels truncated -> added setMinimumWidth(30*char_width_in_lineedit()) to date_hint + fee_note (widgets.py wizard else-branch ~771-810).
- Issue 2a (loop): invalidate_task wait(5)->wait(10) + set self._invalidation_broadcast=True after broadcast; on_success_phase1 (have_to_sign is None) now STOPS with clear message + _add_close_button when _invalidation_broadcast already set (no re-prompt loop). Flag initialised in init ~line 546.
- Issue 2b (label): loop_broadcast_invalidating (dialogs.py:903) now sets wallet.set_label(txid, "BAL Invalidate transaction") on successful broadcast.
- Version bumped 0.4.4 -> 0.4.5 (plugin_base.py, init.py, VERSION, manifest.json).
- Verified: py_compile OK; 255 passed; ruff no new errors; headless label check -> 3 & 2 lines (not truncated).
- ZIP v0.4.5 delivered for testing. NO COMMIT until user confirms.
TASK BATCH #22 — v0.4.6 (6 fixes from allegato 13-18)
- #1 DUST (dialogs.py task_phase1 ~767): dust_heirs dict de-dup -> one row PER HEIR, no willexecutor ref. Was N_exec x N_heirs rows.
- #2 Heirs (dialogs.py build_success_report ~815): single green/bold line "Heirs: a, b, c" via shown_heirs list + one msg_set_status(("Heirs"),...COLOR_OK).
- #3 Scroll (dialogs.py init ~524 + msg_update ~1565): message_label wrapped in QScrollArea (setWidgetResizable, setMaximumHeight 400), wordWrap on label, auto-scroll to bottom (verticalScrollBar.setValue(max)). Close button stays in self.vbox below scroll = always visible.
- #4 Wizard final check (dialogs.py on_next_we ~127, case B): added same block as lists.py check() -> loop Will.needs_server_check + self.bal_window.check_transactions(will) BEFORE self.close(). Root cause: wizard only called build_will_task(), never check_transactions().
- #5 Wizard truncation (widgets.py ~771): REMOVED alignment=AlignLeft flag from date_hint+fee_note (alignment flag blocks horizontal stretch -> wordwrap on narrow sizeHint -> truncated). Added setSizePolicy(Expanding,Minimum) + self.setMinimumWidth(44*cw). Headless verified at 780px dialog -> labels expand to 740px, full text fits.
- #6 Notice (dialogs.py on_success_phase1 ~1336): yellow msg_warning -> "..." black bold + "\n" split after "previous one." (msg_update converts \n->br).
- Version 0.4.5 -> 0.4.6. CHANGELOG #22. 255 tests pass, ruff no new errors.
- ZIP v0.4.6 delivered for testing. NO COMMIT until user confirms.
TASK BATCH #23 — v0.4.7 (4 fixes from testing v0.4.6)
- #1 (allegato1) Scroll height (dialogs.py init ~562-563): scroll_area.setMinimumHeight(500) + setMaximumHeight(700). Was ~140px too short.
- #2 Heirs revert (dialogs.py _build_success_report ~905): REVERTED v0.4.6 "one line" -> ONE heir per line again, green/bold, de-dup via shown_heirs set, skip w!ll3x3c" pseudo-heirs. Reason: long heir names + report now scrolls.
- #3 (allegato2) Wizard line breaks (widgets.py): explicit \n after "(or backup)" in date_hint (~801) and after "miner fees" in fee_note (~831). setWordWrap honours \n.
- #4 ALL-DUST guard. KEY DECISION: guard placed at END of prepare_lists (heirs.py ~577-591), NOT in prepare_transactions. WHY: prepare_transactions only sees the single lowest locktime -> false positive if a later locktime has valid heirs. prepare_lists sees ALL heirs/locktimes with final dust marking (fixed AND percentage). Counts real_heirs (excl. w!ll3x3c") + valid_real_heirs (no "DUST" in HEIR_REAL_AMOUNT); raise HeirAmountIsDustException if real_heirs>0 and valid_real_heirs==0.
- Propagation: HeirAmountIsDustException is NOT WillExecutorFeeException -> skips that except in buildTransactions (heirs.py:646) -> reaches GUI clean, no misleading "error preparing transactions" log. (prepare_transactions guard was REMOVED/reverted to original.)
- GUI: dialogs.py task_phase1 ~803 added
except HeirAmountIsDustExceptionBEFORE genericexcept Exception: red msg_error "All heirs' shares are below the dust limit: the inheritance cannot be created. Increase the amounts or reduce the number of heirs." + return False,None (no sign/check, no empty will in list). - Import: common.py ~65 now imports HeirAmountIsDustException from ...core.heirs (re-exported via
from .common import *; no all). - CRITICAL LEARNING: with FIXED amounts + LARGE balance, leftover funds are REDISTRIBUTED (normalize_perc real=True) so small fixed amounts get a VALID HEIR_REAL_AMOUNT (NOT dust). Real all-dust case = SMALL balance + PERCENTAGE heirs (matches user log 214/316/3 sat). heir list shape when redistributed: [addr, amt, lt, REAL_VALID, "DUST: x", dust_raw].
- 3 new tests (tests/test_core_heirs_extra.py): test_prepare_lists_all_dust_raises (800 sat, 40%/60% -> raises), test_prepare_lists_mixed_dust_continues (5000 fixed + 1% -> no raise), test_prepare_lists_multi_locktime_continues (1% @30d + 5000 @60d -> no raise).
- Version 0.4.6 -> 0.4.7. CHANGELOG #23. 258 tests pass (255+3), ruff no new errors.
- ZIP v0.4.7 delivered for testing. NO COMMIT until user confirms.