fix(plugin): missed-update fixes, server re-check, and bold Building Will results (v0.3.2)

Revert the v0.3.1 double-invalidation change (it caused an inheritance-list
regression: stale/invalidated wills lingered and heir/date updates became
incoherent) and add several targeted missed-update fixes plus a UI refinement.

Revert (v0.3.1 -> v0.3.2):
- Remove Will.mark_invalidated_by_tx() and its call in
  loop_broadcast_invalidating. core/will.py and gui/qt/dialogs.py are restored
  to the working v0.3.0 behaviour. The postpone double-invalidation issue is
  intentionally left open, to be addressed without touching the shared
  broadcast path.

FIX 1 - detect heir removal on Check / Electrum close:
- core/will.py (check_willexecutors_and_heirs): the else-branch now raises
  HeirNotFoundException when a will still carries an heir that is no longer in
  the current heirs set (heir removed), mirroring the existing 'heir added'
  path. Rebuild therefore triggers on Check and on_close (same build_will_task
  path), as decided by the user (manual update only, no auto-rebuild).

FIX 2 - Check queries servers for already-sent wills:
- core/will.py: new Will.needs_server_check(w) returns True for any VALID will
  with a will-executor that is not yet CHECKED (no longer limited to PUSHED).
- gui/qt/lists.py (PreviewList.check): use needs_server_check so wills stuck on
  'New / Not sent' are re-checked instead of reporting 'nothing to do'.

FIX 3 - Settings-dialog hide toggles refresh the list:
- core/plugin_base.py: new sync_hide_filters() re-reads the cached
  _hide_invalidated / _hide_replaced flags from the persisted config.
- gui/qt/window.py (update_all): call sync_hide_filters() before refreshing, so
  toggling 'Hide Invalidated' / 'Hide Replaced' in the Settings dialog (which
  writes the config directly) updates the transaction list immediately instead
  of requiring an Electrum restart.

UI - bold results in the Building Will dialog:
- gui/qt/dialogs.py (BalBuildWillDialog): render the right-side results in bold
  (Ok, Ko, Nothing to do, Skipped, Wait, Timeout, ...) keeping the left-side
  state labels in normal weight. Centralised in msg_ok/msg_error/msg_warning/
  msg_set_status, plus the will-executor push/check rows now show Ok/Ko and
  True/False in bold + colour (green/red).

Tests/tooling:
- tests/test_core_will.py: add test_check_heirs_unchanged_is_coherent,
  test_check_heir_removed_triggers_rebuild, test_check_heir_added_triggers_rebuild,
  test_needs_server_check.
- tests/sim_update_flows.py: real-world update-scenario simulation.
- tests/preview_build_will_dialog.py, tests/preview_we_rows.py: GUI-only
  before/after previews of the bold formatting.
- Bump version to 0.3.2 (VERSION, manifest.json, __init__.py, plugin_base.py).

186 tests pass; smoke test, external-zip test and update-flow simulation OK;
ruff reports only pre-existing star-import false positives.
This commit is contained in:
GenSpark AI Developer
2026-06-16 07:56:11 +00:00
committed by steal
parent e477c5aa5b
commit 365824767b
14 changed files with 669 additions and 163 deletions

105
tests/preview_we_rows.py Normal file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Visual PREVIEW focused on the WILL-EXECUTOR rows of the Building Will dialog.
Reproduces faithfully the three real variants built in dialogs.py:
1. Broadcasting (push) result -> line 774: "{url} : {Ok|Ko}" (plain, no color today)
2. Timeout -> line 783: "{url} : <font red>Timeout - no answer</font>"
3. Checking already-present -> line 825/834:
"checking {url} - {wid} : Waiting"
"checked {url} - {wid} : True/False" (plain, no color today)
Shows BEFORE (current) vs AFTER (proposed: result in bold, keeping label as-is).
Run:
QT_QPA_PLATFORM=offscreen python3 tests/preview_we_rows.py
Writes preview_we_before.png / preview_we_after.png in the repo root.
"""
import os
import sys
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout
from PyQt6.QtCore import Qt
COLOR_ERROR = "#ff0000"
COLOR_OK = "#05ad05"
URL1 = "https://executor.example.org"
URL2 = "https://other-executor.net"
WID = "a1b2c3"
def err(e):
return "<font color='{}'>{}</font>".format(COLOR_ERROR, e)
# ---------------- BEFORE: exactly as the code builds today -----------------
def rows_before():
return [
# 1. push results (plain text, no color/bold today)
"{} : {}".format(URL1, "Ok"),
"{} : {}".format(URL2, "Ko"),
# 2. timeout (already red, not bold)
"{} : {}".format(URL1, err("Timeout - no answer")),
# 3. already-present check
"checking {} - {} : {}".format(URL1, WID, "Waiting"),
"checked {} - {} : {}".format(URL1, WID, "True"),
"checked {} - {} : {}".format(URL2, WID, "False"),
]
# ---------------- AFTER: result portion in bold, label unchanged -----------
def err_after(e):
return "<font color='{}'><b>{}</b></font>".format(COLOR_ERROR, e)
def rows_after():
return [
# 1. push results: color + bold the Ok / Ko outcome
"{} : <font color='{}'><b>{}</b></font>".format(URL1, COLOR_OK, "Ok"),
"{} : <font color='{}'><b>{}</b></font>".format(URL2, COLOR_ERROR, "Ko"),
# 2. timeout: bold the red message
"{} : {}".format(URL1, err_after("Timeout - no answer")),
# 3. already-present check: bold the result
"checking {} - {} : <b>{}</b>".format(URL1, WID, "Waiting"),
"checked {} - {} : <font color='{}'><b>{}</b></font>".format(
URL1, WID, COLOR_OK, "True"
),
"checked {} - {} : <font color='{}'><b>{}</b></font>".format(
URL2, WID, COLOR_ERROR, "False"
),
]
def render(rows, title, path):
full_text = "<br><br>".join(rows).replace("\n", "<br>")
w = QWidget()
w.setStyleSheet("background:#2b2b2b;")
lay = QVBoxLayout(w)
t = QLabel(title)
t.setStyleSheet("color:#ffffff; font-size:15px; font-weight:bold;")
lbl = QLabel(full_text)
lbl.setTextFormat(Qt.TextFormat.RichText)
lbl.setStyleSheet("color:#dddddd;")
f = lbl.font()
f.setPointSize(11)
lbl.setFont(f)
lay.addWidget(t)
lay.addWidget(lbl)
w.resize(560, 320)
w.show()
app.processEvents()
w.grab().save(path)
print(f"saved -> {path}")
if __name__ == "__main__":
app = QApplication(sys.argv)
here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
render(rows_before(), "Will-Executor rows — BEFORE",
os.path.join(here, "preview_we_before.png"))
render(rows_after(), "Will-Executor rows — AFTER",
os.path.join(here, "preview_we_after.png"))