Merge pull request 'v0.5.1: fix Windows Settings-dialog flicker' (#1) from fix-flicker-windows-051 into main

Reviewed-on: kaibot/bal-plugin-ai#1
This commit is contained in:
2026-07-04 08:18:50 +00:00
6 changed files with 62 additions and 259 deletions

View File

@@ -1544,256 +1544,60 @@ confirms the ZIP works).
---
## 26. v0.5.1 - Dynamic Check Alive in BASIC mode + Windows dialog-flicker fix
**Date:** 2026-07-01
**Goal:** Fix two owner-reported bugs: (1) in BASIC mode, anticipating the
delivery time (locktime) to a date earlier than the hidden, stale "Check
Alive" (threshold) default could incorrectly mark the will as expired /
blocked; (2) on Windows only, BAL dialogs (Settings, waiting dialogs) briefly
flashed 1-2 blank "ghost" windows before rendering their real content.
**What changed:**
- **Bug 1 - BASIC mode Check Alive now tracks the delivery time.**
`bal/gui/qt/window.py`
- Added `BalWindow.BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS` (2 hours,
owner-approved) and a new pure `BalWindow.compute_date_to_check(
is_basic_mode, locktime_setting, threshold_setting)` static method.
- In BASIC mode, `date_to_check` (the "Check Alive" reference timestamp) is
now derived LIVE from the current delivery time (`will_settings
["locktime"]`), placed 2 hours before it, instead of reading the hidden,
possibly stale stored `threshold`. This guarantees `date_to_check` is
always earlier than the delivery time, so anticipating/postponing the
delivery date in the wizard never triggers the "locktime is lower than
threshold" guard (`build_inheritance_transaction`) nor
`Will.check_will_expired`/`WillExpiredException` anymore.
- In ADVANCED mode nothing changes: the stored `threshold` is used exactly
as before.
- Root-cause note: `Util.parse_locktime_string` never raises on invalid
input, it silently returns `0`; `compute_date_to_check` now explicitly
treats that sentinel as "unparsable" and falls back to the stored
threshold, so BASIC mode can never compute a bogus near-epoch value.
- `init_class_variables()` now simply calls `compute_date_to_check(...)`.
- **Bug 2 - Windows dialog flicker fixed.**
`bal/gui/qt/window_utils.py` (`show_modal`) and `bal/gui/qt/dialogs.py`
(`BalWaitingDialog.exe`): `bring_to_front()` (`raise_()` +
`activateWindow()`) was being called on dialogs BEFORE `exec()` actually
showed them. On Windows this forces Qt to create the dialog's native window
handle immediately, empty and unpositioned, causing a brief flash of 1-2
blank windows before the real content was drawn (reproduced from a
screen recording; Linux does not show this, since native window creation
there is lazier). Fixed by scheduling `bring_to_front()` via
`QTimer.singleShot(0, ...)` so it runs on the next event-loop iteration,
i.e. after the dialog is already visible with its real content - the
focus/raise behaviour is unchanged, only the timing of when it fires.
- Added `tests/test_group_i_basic_checkalive.py` with 6 tests calling the
real `BalWindow.compute_date_to_check` directly (no GUI/wallet needed):
the 2-hour offset constant, a fresh "1y" default, the exact reported
"anticipate to 1 month" scenario, a sweep of delivery dates always staying
before locktime, ADVANCED mode being unaffected, and the unparsable-input
fallback (which caught and fixed a real edge case during development: a
0-timestamp sentinel instead of an exception).
- Version bumped 0.5.0 -> 0.5.1 (`plugin_base.py`, `__init__.py`, `VERSION`,
`manifest.json`).
**Verification:**
- Full test suite: `272 passed` (266 previous + 6 new), plus the same 2
pre-existing, unrelated failures already present before this change
(`test_core_plugin_base.py::test_default_will_settings` /
`test_validate_will_settings`, expecting the old `baltx_fees=100` default
that v0.5.0 already changed to `20`).
- `ruff`: no new errors (only pre-existing star-import / F841 noise).
- The Check Alive fix (Bug 1) is fully covered by automated tests. The
Windows flicker fix (Bug 2) was diagnosed from a screen recording and
reasoned from the Qt/Windows native-window-creation behaviour; it cannot be
reproduced or automatically verified on this Linux environment, so the
owner needs to confirm on Windows that the flicker is gone.
**Outcome:** DONE (delivered as test ZIP v0.5.1; commit only after the owner
confirms both fixes work, especially the Windows flicker one).
---
## 27. v0.5.2 - Real fix for the Windows Settings-dialog flicker (root cause found by diffing against v0.4.8)
**Date:** 2026-07-01
**Goal:** The v0.5.1 attempt at fixing the Windows-only "ghost window" flicker
on the BAL Settings dialog (deferring `bring_to_front()` via
`QTimer.singleShot`) did NOT fix it, per owner confirmation after testing on
Windows. This entry documents the corrected root cause and the actual fix.
**Root cause (confirmed by diff, not guesswork):** the owner provided the
last known-good release (v0.4.8, no flicker) for direct comparison.
`bal/gui/qt/window_utils.py` (`show_modal`/`bring_to_front`) turned out to be
byte-identical between v0.4.8 and v0.5.0 (aside from the v0.5.1 attempt),
proving that code was never the cause. Diffing `bal/gui/qt/plugin.py`
instead showed that `settings_dialog()` gained, between v0.4.8 and v0.5.0:
(a) ~17 ADVANCED-only setting rows that are shown/hidden via `setVisible()`
right at dialog construction time (to reflect the current BASIC/ADVANCED
mode before the dialog is ever shown), and (b)
`outer.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)`, which did
**not** exist anywhere in v0.4.8's GUI code. `SetFixedSize` forces Qt to
recompute and enforce the dialog's exact size on every layout invalidation -
including while those ~17 rows are being hidden for the first time, before
the dialog has ever been shown. On Windows, that live geometry renegotiation
overlapping with native window creation is what produced the blank "ghost"
window flash (reproduced from the owner's screen recording).
**What changed:**
- `bal/gui/qt/plugin.py` (`settings_dialog`): removed
`outer.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)`, restoring
the sizing behaviour v0.4.8 already had (no forced constraint). Added an
explicit `d.adjustSize()` right before `show_modal(d)` so the dialog still
opens at its natural, correctly laid-out size (now computed once, instead
of being continuously re-enforced). The dynamic BASIC/ADVANCED row
visibility feature itself is fully preserved - only the size-constraint
coupling that caused the flicker was removed.
- Removed the now-unused `QLayout` import.
**Verification:**
- Full test suite: `272 passed`, same 2 pre-existing, unrelated failures as
before (stale `baltx_fees=100` expectation vs the v0.5.0 default of `20`).
- `ruff`: no new errors (only pre-existing star-import noise).
- Diagnosis confirmed by direct code comparison against v0.4.8 (provided by
the owner) rather than inference alone. The fix itself still cannot be
verified on this Linux environment (Windows-only symptom): the owner needs
to confirm on Windows that the flicker is gone.
**Outcome:** DONE (delivered as test ZIP v0.5.2; commit only after the owner
confirms the flicker is actually gone this time).
---
## 28. v0.5.3 - Windows Settings-dialog flicker: corrected fix (drop bring_to_front before exec(), not just defer it)
**Date:** 2026-07-01
**Goal:** v0.5.2 (removing `SetFixedSize`) did NOT fix the Windows flicker
either, per owner confirmation ("è come prima" - unchanged). This entry
documents the corrected fix, based on new evidence from the owner: the
flicker only ever happens with the Settings dialog in ADVANCED mode (many
more visible rows, much larger dialog) and never in BASIC mode (few rows,
small dialog) - always with the same intensity, not intermittent.
**Root cause (corrected):** `show_modal()` (`bal/gui/qt/window_utils.py`)
called `bring_to_front(dialog)` (`raise_()` + `activateWindow()`) BEFORE
`dialog.exec()`. The v0.5.1 attempt only *deferred* this call by one event
loop tick (`QTimer.singleShot(0, ...)`) instead of removing it, which turned
out to still fire too early relative to Windows finishing the dialog's
native window layout/paint - reproducing the same flicker. The owner's
BASIC-vs-ADVANCED observation is the key evidence: `raise_()`/
`activateWindow()` forcing native window creation before the layout has
settled produces a flash whose visible severity scales with how different
the dialog's final size is from its premature/unlaid-out state - which is
exactly why a much bigger ADVANCED-mode dialog flickers noticeably while the
small BASIC-mode one does not.
**What changed:**
- `bal/gui/qt/window_utils.py` (`show_modal`): removed the `bring_to_front()`
call (deferred or otherwise) entirely. `QDialog.exec()` already shows,
raises and activates a **modal** dialog on its own as part of entering its
modal event loop, so the call was always redundant for this case - and, on
Windows, actively harmful. `bring_to_front()` itself is untouched and is
still used (correctly) by `show_on_top()`, for the few genuinely
**non-modal** dialogs that use `.show()` instead of `.exec()` and do need
an explicit raise/activate.
- Removed the now-unused `QTimer` import.
- `bal/gui/qt/dialogs.py` (`BalWaitingDialog.exe`): applied the same
corrected fix for consistency (same redundant-before-exec() pattern, same
underlying cause), even though the owner did not report flicker there -
removed `bring_to_front()`/`QTimer.singleShot` and kept only `self.exec()`.
**Verification:**
- Full test suite: `272 passed`, same 2 pre-existing, unrelated failures as
before.
- `ruff`: no new errors (only pre-existing star-import / F841 noise).
- As with v0.5.1/v0.5.2, this is a Windows-only symptom that cannot be
reproduced or verified on this Linux environment; the owner needs to
confirm on Windows.
**Outcome:** DONE (delivered as test ZIP v0.5.3; commit only after the owner
confirms the flicker is actually gone).
---
## 29. v0.5.4 - REAL fix for the Windows Settings-dialog flicker (found by Windows-side bisection with the owner)
## 25. v0.5.1 - Fix Windows Settings-dialog flicker (rollback to v0.5.0 + flicker fix only)
**Date:** 2026-07-02
**Goal:** v0.5.3 (dropping `bring_to_front()` before `exec()`) did NOT fix the
flicker either, per owner confirmation ("è come prima" - unchanged). This
entry documents the actual root cause, found through a structured bisection
process where the owner tested a series of isolated diagnostic ZIPs directly
on Windows (since the bug cannot be reproduced on Linux), and the
corresponding real fix, **confirmed working by the owner on Windows**.
**Context:** Starting from the clean v0.5.0 codebase (a set of later
experimental builds, 0.5.1-0.5.4, were discarded because they introduced
other problems). This release applies ONLY the Windows Settings-dialog
flicker fix and changes nothing else - in particular, NO changes to locktime,
delivery time, Check Alive, or any will-building logic. Only
`bal/gui/qt/plugin.py` is touched (verified byte-for-byte against v0.5.0:
every other file is identical).
**Bisection process (each step tested live on Windows by the owner):**
1. A dialog stripped down to almost nothing (2 checkboxes, no ADVANCED-only
rows, none of the widgets/layout features added since v0.4.8) did **not**
flicker → the cause is in the dialog's content, not in how/where it is
opened, nor in `show_modal`/`bring_to_front` (already ruled out in
v0.5.1/v0.5.3), nor in `SetFixedSize` (already ruled out in v0.5.2).
2. Re-adding *only* the "User Type" combo + its dynamic show/hide mechanism
flickered (mildly); re-adding *only* the new text/input fields (Event
summary/description, Welist Server, Calendar app) did **not** → narrowed
to the combo/visibility mechanism.
3. A plain `QComboBox` alone (no confirmation dialog, no dynamic show/hide)
did **not** flicker; the construction-time `setVisible()` toggle alone
(no combo at all) **did** flicker → isolated to the visibility mechanism
itself.
4. A version of that same toggle that hides each ADVANCED-only widget
**before** adding it to the layout (instead of adding it visible and
hiding it afterwards) did **not** flicker → fix validated.
**Bug:** On Windows only, opening the BAL Settings dialog in ADVANCED mode
briefly flashed one or two blank "ghost" windows before the dialog rendered
correctly. On Linux it did not occur.
**Root cause:** the ~17 ADVANCED-only rows (Welist Server, Number of
reminders, Event summary/description, Calendar app, Auto-sign, and their
per-row reset buttons) were added to the settings grid **visible**, then all
hidden together with `setVisible(False)` in BASIC mode, in a single loop
*after* they were already part of the live layout. On Windows, that
visiblehidden transition inside an already-populated layout - happening
while the dialog's native window was still being created - forced a
re-layout that flashed the dialog on screen. It only showed in ADVANCED mode
because BASIC mode is when the widgets get hidden (so ADVANCED is when they
stay visible and the dialog is at its largest, making any transient
mis-layout most noticeable) - consistent with the owner's observation that
severity tracked dialog size.
**Root cause (found by bisection, each step tested on Windows by the user):**
the ~24 ADVANCED-only widgets (Welist Server, Number of reminders, Event
summary/description, Calendar app, Auto-sign, and their per-row reset buttons)
were added to the settings grid *visible* and then hidden all at once with a
`setVisible(False)` loop AFTER they were already part of the live layout. On
Windows, that visible->hidden transition inside an already-populated layout,
happening while the dialog's native window was being created, forced a
re-layout that flashed the dialog on screen. It only appeared in ADVANCED
mode because that is when those rows are toggled and the dialog is largest.
A minimal dialog (no ADVANCED-only rows) never flickered; re-adding only the
construction-time `setVisible()` toggle reproduced it; hiding each widget
BEFORE adding it to the layout eliminated it (all confirmed on Windows).
**What changed:**
**What changed (`bal/gui/qt/plugin.py`, `settings_dialog` only):**
- `bal/gui/qt/plugin.py` (`settings_dialog`): added a `basic_init` flag and a
`_hide_if_basic(widget)` helper right after the dialog is created. Every
ADVANCED-only widget is now wrapped in `_hide_if_basic(...)` at its
`grid.addWidget(...)` call, so its final visibility is set **before** it
ever joins the layout - it never transitions visiblehidden inside a live
layout. Removed the old post-hoc loop that used to hide all ~17 widgets at
once after they were already added. The runtime BASIC/ADVANCED toggle
(`on_user_type_change`, used while the dialog is already open and visible)
is unaffected and still works exactly as before - only the one-time
construction-time visibility set-up changed.
- Reverted the ineffective v0.5.2 attempt (`SetFixedSize` removal /
`adjustSize()`) and the ineffective v0.5.3 attempt (removing
`bring_to_front()` from `show_modal()`/`BalWaitingDialog.exe()`) back to
their original v0.5.0 behaviour, since bisection proved neither was the
cause; keeping them would only have added unnecessary risk/diff.
- Added a `basic_init` flag and a `_hide_if_basic(widget)` helper right after
the dialog is created. Each ADVANCED-only widget is now wrapped in
`_hide_if_basic(...)` at its `grid.addWidget(...)` call, so its final
visibility is set BEFORE it ever joins the layout - it never transitions
visible->hidden inside a live layout.
- Removed the old post-hoc `setVisible()` loop that hid all ~24 widgets at
once after they were already added (the flicker cause).
- The runtime BASIC<->ADVANCED toggle (`on_user_type_change`, used while the
dialog is already open and visible) is unaffected and still works exactly
as before - only the one-time construction-time visibility set-up changed.
**Verification:**
- Full test suite: `272 passed`, same 2 pre-existing, unrelated failures as
before (stale `baltx_fees=100` expectation vs the v0.5.0 default of `20`).
- Full test suite: `266 passed` (same as clean v0.5.0), plus the same 2
pre-existing, unrelated failures already present in v0.5.0
(`test_core_plugin_base.py::test_default_will_settings` /
`test_validate_will_settings`, expecting the old `baltx_fees=100` default
that v0.5.0 already changed to `20`).
- `ruff`: no new errors (only pre-existing star-import noise).
- **Confirmed fixed by the owner on Windows** (the actual reporting
environment), after testing the full real Settings dialog with this fix
applied - not just the isolated diagnostic build.
- Confirmed on Windows by the user that the fix (validated as an isolated
diagnostic build) removes the flicker.
- Only `bal/gui/qt/plugin.py` differs from clean v0.5.0; all other files,
including all locktime/date/Check-Alive logic, are byte-identical.
**Outcome:** DONE. Confirmed resolved by the owner on Windows.
**Outcome:** DONE (delivered as test ZIP v0.5.1; commit only after the user
confirms the ZIP works on Windows).

View File

@@ -1 +1 @@
0.5.4
0.5.1

View File

@@ -34,4 +34,4 @@ The plugin targets Electrum 4.7.2 (the last stable release exposing
``json_db.register_dict``) and PyQt6.
"""
__version__ = "0.5.4"
__version__ = "0.5.1"

View File

@@ -91,7 +91,7 @@ class BalPlugin(BasePlugin):
"""
_version = None
__version__ = "0.5.4" # AUTOMATICALLY GENERATED DO NOT EDIT
__version__ = "0.5.1" # AUTOMATICALLY GENERATED DO NOT EDIT
# Command used to open an .ics calendar file, per operating system.
default_app = {

View File

@@ -378,29 +378,28 @@ class Plugin(BalPlugin):
lbl_logo = QLabel()
lbl_logo.setPixmap(qicon)
# WINDOWS FLICKER FIX (root cause found by bisection with the owner
# testing on Windows): the ADVANCED-only rows used to be added to the
# WINDOWS FLICKER FIX (root cause found by bisection, tested on Windows
# by the user): the ADVANCED-only rows below used to be added to the
# grid *visible* and then hidden all at once with setVisible(False)
# AFTER they were already in the layout (see the old block near the
# end of this function). On Windows that visible->hidden transition,
# happening inside an already-populated layout while the dialog's
# native window is being created, forced a live re-layout that flashed
# the dialog on screen (the "ghost window" the owner saw). It only
# showed in ADVANCED because that is the larger dialog. The fix,
# validated step by step on Windows, is to set each ADVANCED-only
# AFTER they were already part of the layout. On Windows that
# visible->hidden transition, inside an already-populated layout while
# the dialog's native window is being created, forced a live re-layout
# that flashed the dialog on screen (the "ghost window" flicker). It
# only showed in ADVANCED mode because that is the larger dialog. The
# fix, validated step by step on Windows, is to set each ADVANCED-only
# widget's visibility BEFORE it is ever added to the layout, so it
# never transitions visible->hidden inside a live layout.
#
# ``basic_init`` is the current mode; ``_hide_if_basic(w)`` hides a
# widget immediately (at creation time) when in BASIC mode and returns
# it, so it can be wrapped around each ADVANCED-only widget inline.
# widget immediately (at creation/add time) when in BASIC mode and
# returns it, so it can be wrapped inline around each ADVANCED-only
# widget at its grid.addWidget(...) call.
basic_init = str(self.USER_TYPE.get()).lower() != "advanced"
def _hide_if_basic(w):
"""Hide *w* now (before it is added to any layout) if in BASIC
mode, and return it. Used to give ADVANCED-only widgets their
final visibility up-front, avoiding the Windows relayout flicker
that a later setVisible(False) inside the populated grid caused."""
mode, and return it. Avoids the Windows relayout flicker that a
later setVisible(False) inside the populated grid caused."""
if basic_init:
w.setVisible(False)
return w

View File

@@ -1,7 +1,7 @@
{
"name": "bal",
"fullname": "Bitcoin After Life",
"version": "0.5.4",
"version": "0.5.1",
"description": "Provides free and decentralized Bitcoin inheritance support. Build time-locked 'will' transactions that transfer funds to your heirs if you stop refreshing them (dead-man's switch), optionally relayed by will-executor servers.",
"author": "Svatantrya",
"licence": "MIT",