Files
bal-electrum-plugin/bal/gui/qt/theme.py
GenSpark AI Developer a394cde0b5 feat(will): invalidate signed will on postpone + add Server status column
Postpone safety (Strategy B):
- A signed/sent will carries an immutable locktime; postponing the delivery
  time previously did nothing, so a will-executor could still broadcast the
  old (earlier-locktime) transaction and execute the inheritance too early.
- core/will.py: add WillPostponedException and detect postpone by comparing
  the requested locktime against w.tx.locktime (the locktime frozen in the
  signed transaction) instead of the in-memory heir entry, which is updated
  together with the new value and would always compare equal.
- gui/qt/dialogs.py (BalBuildWillDialog.task_phase1, the real path used by
  Tools -> Prepare): handle WillPostponedException before NotCompleteWill;
  return (None, tx) to trigger sign + broadcast of the invalidation, then the
  user presses Prepare again to rebuild/re-sign/re-send (two explicit steps).
- gui/qt/window.py: mirror the branch in build_inheritance_transaction with an
  explanatory message; wording aligned to the 'Prepare' button.
- gui/qt/common.py: export WillPostponedException.
- A postpone on a will that was never signed/sent just rebuilds (no on-chain
  fee).

Server status column:
- gui/qt/lists.py: add a dedicated 'Server' column to PreviewList with an
  always-readable label and a tooltip (will-executor URL + state).
- gui/qt/theme.py: add server_status_text() and server_status_tooltip(),
  reusing the existing status flags.
- gui/qt/common.py: export the new theme helpers.

Docs: update README.md, bal/README.md and CHANGELOG_REFACTOR.md.

Tests: 182 passed; smoke + external-zip OK; ruff has no new real findings.
2026-06-15 21:52:22 +00:00

98 lines
3.8 KiB
Python

"""
bal.gui.qt.theme
================
Pure presentation helpers for the Qt layer.
This is where colours and other look-and-feel decisions live, kept apart from
the core inheritance logic. In particular it hosts :func:`status_color`, which
used to be ``WillItem.get_color()`` inside ``will.py``.
The status flags themselves are computed by the core layer
(:class:`bal.core.will.WillItem`); this module only translates a will item's
status into a colour for the transaction list / detail views.
"""
# Status -> hex colour. The first matching status (checked in priority order)
# wins. These are exactly the colours the original ``WillItem.get_color`` used,
# so the GUI looks identical after the refactor.
#
# The order matters: e.g. an INVALIDATED tx must show orange even if it also
# carries other flags, so INVALIDATED is checked before everything else.
_STATUS_COLOR_PRIORITY = (
("INVALIDATED", "#f87838"), # orange - tx can no longer be mined
("REPLACED", "#ff97e9"), # pink - superseded by another tx
("CONFIRMED", "#bfbfbf"), # grey - already mined
("PENDING", "#ffce30"), # yellow - in mempool, waiting
)
# Default colour used when no status in the priority list matches.
_DEFAULT_COLOR = "#ffffff"
def status_color(will_item) -> str:
"""Return the display colour (``"#rrggbb"``) for a :class:`WillItem`.
This is a faithful, behaviour-preserving port of the old
``WillItem.get_color()`` method. The slightly irregular handling of the
push/check states (which is not a simple priority list) is reproduced
exactly as in the original code.
"""
# First, the simple priority-ordered statuses.
for status, color in _STATUS_COLOR_PRIORITY:
if will_item.get_status(status):
return color
# The remaining states need the original branching because of the
# CHECK_FAIL / CHECKED interaction.
if will_item.get_status("CHECK_FAIL") and not will_item.get_status("CHECKED"):
return "#e83845" # red - server check failed
elif will_item.get_status("CHECKED"):
return "#8afa6c" # green - server confirmed it stored the tx
elif will_item.get_status("PUSH_FAIL"):
return "#e83845" # red - failed to push to will-executor
elif will_item.get_status("PUSHED"):
return "#73f3c8" # teal - pushed to will-executor
elif will_item.get_status("COMPLETE"):
return "#2bc8ed" # blue - signed
else:
return _DEFAULT_COLOR
def server_status_text(will_item) -> str:
"""Return a short, human-readable label describing the state of a will
item on the will-executor servers (the online inheritance backup).
This is shown in the dedicated "Server" column of the transaction list so
the user always knows whether each inheritance transaction is actually
stored on the will-executor servers, regardless of the row colour.
"""
from electrum.i18n import _
if will_item.get_status("CHECK_FAIL") and not will_item.get_status("CHECKED"):
return _("Not on server")
if will_item.get_status("CHECKED"):
return _("Confirmed on server")
if will_item.get_status("PUSH_FAIL"):
return _("Send failed")
if will_item.get_status("PUSHED"):
return _("Sent (not checked)")
if will_item.get_status("COMPLETE"):
return _("Signed (not sent)")
return _("Not sent")
def server_status_tooltip(will_item) -> str:
"""Return a detailed tooltip for the "Server" column, including the
will-executor URL (if any) and the current server state."""
from electrum.i18n import _
url = None
we = getattr(will_item, "we", None)
if we:
url = we.get("url")
state = server_status_text(will_item)
if url:
return "{}: {}\n{}".format(_("Will-Executor"), url, state)
return "{}\n{}".format(_("No will-executor"), state)