
## 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):**
1. 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".
2. 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."
3. 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 ... <timestamp>" 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):**
1. RENAME the inheritance label: "BAL Transaction" -> "BAL Inheritance transaction".
2. CHANGE the inheritance label colour from RED to GREEN.
3. 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)`
  in `bal/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()` in `bal/gui/qt/plugin.py:372`.
  It uses a grid with the helper `add_widget(grid, label, widget, row, help_)`
  (defined in `bal/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):**
1. In `settings_dialog()`: create `heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)`.
2. 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.
3. Add `(self.NO_WILLEXECUTOR, heir_no_willexecutor, "check")` to the `resets` list
   in on_reset_defaults, so "Reset setting" returns it to ON.
4. 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):
1. CHECK and WIZARD must behave IDENTICALLY.
2. First show a WARNING popup (no more "use the top-right menu Tools -> Invalidate" text).
3. 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.
4. 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)
1. 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.
2. In SIMPLE mode, locktime entry uses normal DATA mode (calendar), not RAW.
3. 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()`;
  if `date_to_check < now` -> raises CheckAliveError (~469). Default relative dates:
  threshold "30d", locktime "1y" (plugin_base.py:341-342; defaults built at ~320-337).
  `threshold` is 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
1. In SIMPLE mode, what should CHECK ALIVE do by default? (a) disabled/no prompt,
   (b) auto-set to a value (which?), (c) something else.
2. Exact rule to auto-detect "advanced" wallets to force ADVANCED mode on open.
3. 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:**
1. 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.
2. 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.
3. 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__` reads `is_basic_mode()` ONCE
  (line ~293) and, in BASIC, hides the Raw/Date combo (line ~336 `self.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 from `BalWindow.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 touches `self.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),
  and `update_combo_setting_widgets` -> `set_index` sets the combo INDEX to Raw "behind the scenes" — but the
  combo is still `setVisible(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 to `BalTimeEditWidget` (e.g. `apply_user_type_visibility()`)
that re-reads `is_basic_mode()` and does `self.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 from
`WillSettingsWidget.apply_user_type_visibility()` for BOTH `locktime` AND `threshold` (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 <flag>" 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
1. "Checking variables"
2. "Checking your will"
3. "Building your will"
4. "Invalidating old will"            (only when an invalidation is triggered)
5. "Signing your will"                (phase 2)
6. "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) |
| <expired notice from exception>                                | ORANGE  | WillExpiredException after rebuild -> returns "invalidate_classic" (~683) |
| <error text>                                                   | RED     | any other Exception during build (~688) |
| <heir>,<amount> is DUST -> Excluded from will <wid>            | 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 |
| <broadcast error msg>  | 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 |
| <url> : Ok                                     | GREEN | that server accepted the tx (-> PUSHED) |
| <url> : Ko                                     | RED   | that server rejected (-> PUSH_FAIL) |
| <url> : Timeout - no answer                    | RED   | server did not answer in time (-> PUSH_FAIL) [related to #02] |
| checking <url> - <wid> : Waiting               | plain | verifying an "already present" server |
| checked <url> - <wid> : <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").
- 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.
- 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)<br>"
      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:
  1. check_willexecutors_and_heirs -> "heir: erede002new not found" -> HeirNotFoundException
     -> "not complete erede002new true" -> message "Found CHANGES..." -> have_to_build=True. OK
  2. build_will() -> "txs built: {3 tx}" (new tx created). OK
  3. dialogs.py:697 self.bal_window.check_will() -> is_will_valid -> check_willexecutors_and_heirs
     AGAIN -> "heir: erede001 not found" -> raises HeirNotFoundException("erede001").
  4. 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
     then `return 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 -> "<b>...</b>" 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 HeirAmountIsDustException` BEFORE generic `except 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.
