Files
bal-electrum-plugin/.agent_memory_tasks.md
donkey-ai 646a33f2f5 feat(v0.4.7): report area 500px, heirs one-per-line, wizard line breaks, ALL-DUST guard
Owner-approved changes after testing v0.4.6, plus accumulated v0.4.x work,
task-tracking notes, and an updated project HANDOFF document.

The four v0.4.7 changes:

1. Report area (BalBuildWillDialog) opens 500px tall (min) up to 700px (max),
   then the scrollbar takes over. Previously it opened ~140px (too short).

2. Heirs are listed ONE per line again (green, bold) in _build_success_report,
   reverting the v0.4.6 single-line form. Heir names can be long and the report
   now scrolls, so compression is no longer needed.

3. Two wizard texts get an explicit line break: after "(or backup)" in the date
   hint and after "miner fees" in the fee note (widgets.py).

4. ALL-DUST guard: when EVERY heir's share is below the Bitcoin dust limit, the
   inheritance would pay nobody. Heirs.prepare_lists now raises
   HeirAmountIsDustException at the end (where all heirs across all locktimes
   are known with their final dust state), and dialogs.task_phase1 shows a clear
   RED message and stops without building/signing/checking. A mix of dust +
   valid heirs keeps building normally. The guard is intentionally in
   prepare_lists, NOT prepare_transactions (which only sees the lowest locktime
   and would false-positive). HeirAmountIsDustException is imported in common.py.

Tests: 3 new tests in test_core_heirs_extra.py pin the dust behaviour (all-dust
raises; mixed continues; multi-locktime continues). Full suite: 258 passed.
ruff: no new errors. Version bumped 0.4.6 -> 0.4.7 (4 files). CHANGELOG #23.

Also adds/updates HANDOFF.md so any future AI (Claude or another model) can
resume the project with full context (rules, layout, build/test/lint, dust
logic, git flow), and records the task-tracking notes in
.agent_memory_tasks.md.
2026-06-28 23:02:25 -04:00

830 lines
59 KiB
Markdown

## TASK A (proposto, NON ancora avviato) — Migliorare il messaggio "WILL EXPIRED"
**Origine:** osservato dall'utente negli screenshot del 23/06/2026 (will scaduto -> percorso invalidate + re-sign). La LOGICA è corretta; si migliora solo la UX/chiarezza del messaggio.
**3 miglioramenti approvati dall'utente (da implementare poi, in inglese, regole R1-R4 + zip-first):**
1. Tradurre il timestamp Unix grezzo (es. 1782118800) in data leggibile.
Esempio: invece di "Will Expired 9f1b0a75...: 1782118800"
mostrare "Will expired (locktime 2026-06-22 11:00 UTC) - too late to anticipate, will invalidate and re-sign".
2. Rendere il messaggio INFORMATIVO e non un ERRORE: il rosso sembra un errore,
ma e' un flusso normale. Usare colore di avviso (arancione) o aggiungere frase
tipo "This is expected: the will is past its locktime, switching to invalidate + re-sign."
3. Accorciare l'hash del will per leggibilita' (es. primi 8 + ultimi 4 caratteri).
**Note tecniche da fare in DISCOVER quando si avvia:**
- Trovare il punto del codice che genera la stringa "Will Expired ... <timestamp>" (probabilmente nel wizard "Building Will").
- Verificare se il colore rosso e' impostato li' (rich text / stylesheet).
- Seguire METHOD: DISCOVER -> PLAN (attendere OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release solo dopo conferma.
## TASK B (proposto, NON ancora avviato) — Etichette descrizione transazioni in Cronologia
**Origine:** osservato dall'utente nello screenshot Cronologia del 23/06/2026.
Nella tab "Cronologia" di Electrum (transazioni on-chain), il plugin BAL scrive
una descrizione colorata nella colonna "Descrizione".
**Stato attuale:**
- La transazione di EREDITA' e' etichettata "BAL Transaction" (colore rosso).
- La transazione di INVALIDATE NON ha alcuna descrizione.
**Modifiche richieste (approvate dall'utente):**
1. RINOMINARE l'etichetta dell'eredita': "BAL Transaction" -> "BAL Inheritance transaction".
2. CAMBIARE il colore dell'etichetta eredita' da ROSSO a VERDE.
3. AGGIUNGERE una nuova etichetta per le transazioni di invalidate:
"BAL Invalidate transaction" in colore ARANCIONE (oggi compaiono senza descrizione).
**Riepilogo finale desiderato:**
| Transazione | Etichetta desiderata | Colore |
|-------------|-----------------------------|-----------|
| Eredita' | BAL Inheritance transaction | VERDE |
| Invalidate | BAL Invalidate transaction | ARANCIONE |
**Note tecniche da fare in DISCOVER quando si avvia:**
- Trovare nel codice dove viene impostata l'etichetta "BAL Transaction"
(probabilmente wallet.set_label(txid, ...) o simile) e dove/come e' impostato il colore.
- Capire come il plugin distingue una tx di eredita' da una di invalidate, per
applicare l'etichetta corretta a ciascuna. La tx di invalidate oggi NON riceve
label -> trovare il punto dove viene creata/broadcastata e aggiungere lì set_label.
- Verificare se il colore (rosso) e' gestito da Electrum o dal plugin, e come
impostare il verde per l'eredita'.
- Seguire METHOD: DISCOVER -> PLAN (attendere OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release solo dopo conferma.
## TASK C (proposto, NON ancora avviato) — Checkbox "no will-executor" anche in Plugin Settings
**Origine:** richiesta utore del 23/06/2026.
Nel wizard "Create your WILL", finestra di download will-executor, esiste il checkbox
"Add transactions without willexecutor". L'utente vuole lo STESSO checkbox anche nella
finestra "Plugin settings" (stesso stile delle altre righe), default ON, con un
HelpButton che spiega la funzione.
**Testo del tastino di aiuto (fornito dall'utente, da usare 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."
**SCOPERTA IMPORTANTE (DISCOVER gia' fatto):**
- La config ESISTE GIA': `self.NO_WILLEXECUTOR = BalConfig(config, "bal_no_willexecutor", True)`
in `bal/core/plugin_base.py:193` (default True = ON). Quindi NON va creata.
- Il checkbox del wizard e' in `bal/gui/qt/lists.py:916-918`:
hbox.addWidget(QLabel(_("Add transactions without willexecutor")))
heir_no_willexecutor = BalCheckBox(self.bal_plugin.NO_WILLEXECUTOR)
-> usa BalCheckBox legato alla stessa config. Aggiungendo lo stesso checkbox nelle
settings, i due rimangono sincronizzati automaticamente (stessa BalConfig).
- La finestra Settings e' `settings_dialog()` in `bal/gui/qt/plugin.py:372`.
Usa una griglia con la helper `add_widget(grid, label, widget, row, help_)`
(definita in `bal/gui/qt/common.py:98`) che mette: QLabel(col0), widget(col1),
HelpButton(col2). Le righe attuali vanno 1..8 (8 = bottone Rebroadcast).
- Esiste gia' codice COMMENTATO che faceva esattamente questo (plugin.py:382 e
536-542): `# heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)` e un
add_widget "Backup Transaction" -> si puo' riattivare/adattare.
- C'e' anche un blocco "Reset setting" (on_reset_defaults, plugin.py:560-596) con
una lista `resets = [...]`: per coerenza la nuova checkbox va AGGIUNTA a quella
lista cosi' il Reset la riporta al default (ON).
**PLAN bozza (da rifinire e far approvare quando si avvia):**
1. In `settings_dialog()`: creare `heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR)`.
2. Aggiungere una riga con `add_widget(grid, "<label>", heir_no_willexecutor, <row>, "<help text utente>")`.
- Decidere label breve (es. "No will-executor" / "Backup will (no will-executor)") -> CHIEDERE ALL'UTENTE quale label preferisce nella colonna sinistra.
- Decidere la riga: inserire prima del Rebroadcast (riga 8), eventualmente rinumerando le righe successive.
3. Aggiungere `(self.NO_WILLEXECUTOR, heir_no_willexecutor, "check")` alla lista `resets`
in on_reset_defaults, cosi' il "Reset setting" la riporta a ON.
4. Mantenere lo stile esistente (HelpButton, griglia). Default gia' ON via config.
**DOMANDA DA PORRE PRIMA DI EXECUTE:** quale etichetta breve mostrare nella colonna
sinistra delle settings? (l'utente ha fornito solo il testo del tastino di aiuto).
- Seguire METHOD: DISCOVER(fatto) -> PLAN (attendere OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release solo dopo conferma.
## TASK D (proposto, NON ancora avviato) — Rinominare il tastone del wizard
**Origine:** richiesta utente del 23/06/2026.
Il tastone grande che apre il wizard mostra "Create your will" e l'utente vuole
cambiarlo in "Build your will" (aveva scritto "BUIL YOUR WILL" -> refuso per BUILD).
**SCOPERTA (DISCOVER gia' fatto):**
- Il bottone e' in `bal/gui/qt/lists.py:473`:
wizard = QPushButton(" " + _("Create your will"))
- INCOERENZA esistente: il tooltip dello STESSO bottone (lists.py:483) dice gia'
wizard.setToolTip(_("Wizard - Build your will"))
e tutti i commenti del codice (8 occorrenze) chiamano il wizard "Build your will".
Quindi cambiare il testo del bottone in "Build your will" UNIFORMA tutto.
**PARERE AGENTE (concordato con l'utente):** "Build your will" e' la scelta migliore
(coerenza con tooltip/commenti/config; "Build" comunica meglio il processo guidato a step).
**FORMA SCELTA DALL'UTENTE:** "Build Your Will" (title case).
**PLAN bozza:** sostituire la sola stringa "Create your will" -> "Build Your Will"
(title case) in `lists.py:473`. Verificare che non ci siano altre occorrenze del
testo del bottone da allineare. Lasciare invariato il tooltip (gia' "Build your will").
- Seguire METHOD: DISCOVER(fatto) -> PLAN (attendere OK) -> EXECUTE -> VERIFY -> ZIP-first -> commit/PR/release solo dopo conferma.
================================================================
## PENDING TASK (post-v0.3.9) — UNIFY INVALIDATE PROCEDURE — NOT STARTED
## Status: WAITING for user to provide MORE input before planning/acting.
## User said: "OPZIONE A ... ma aspetta prima di agire, che ho altro da darti in pasto"
## then refined the requirement (see below), then: "pero attendi altro, intanto salva questo punto"
================================================================
### 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: "ti aggiungo altri punti, da mettere in lista delle cose da fare"
## 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 (verbatim, IT):** "dopo aver fatto una rendita e il wizard, il plugin dice 'heir not found',
ma in realtà è stato solo anticipata la data, il messaggio informativo è sbagliato, ma il resto funziona bene."
**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 (verbatim, IT):** "quando scarico dal wizard la lista dei will executor, il plugin deve spuntare
di verde solo i server che hanno risposto correttamente, e che hanno il pallino del ping verde.
altrimenti poi si impalla tutto il plugin e continua a fare broadcast a quelli che non rispondono bene.
meglio scartare alla fonte i server che non rispondono subito bene. questo deve valere ogni volta che
scarico la lista server; se la seconda volta scarico e un server che prima non rispondeva ora risponde,
il plugin lo aggiunge alla lista server."
**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 (verbatim, IT):** "come già detto prima con te, la transazione invalidate non ha scritto
'BAL invalidate transaction' in cronologia del wallet, se fatto invalidate dalla finestra che si apre
in automatico. mentre lo scrive solo quando faccio invalidate dal menu TOOLS, Invalidate manualmente."
**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.
### 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 (Opzione 2):** change BOTH the CHECK window (dialogs.py) AND window.py for consistency.
**User said:** "ma metti tutto in lista non fare modifiche per ora" (just add to list, do NOT modify yet).
### 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.