diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f03561..7daa7ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 -visible→hidden 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 visible→hidden 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). diff --git a/bal/VERSION b/bal/VERSION index 167b000..5d4294b 100644 --- a/bal/VERSION +++ b/bal/VERSION @@ -1 +1 @@ -0.5.4 \ No newline at end of file +0.5.1 \ No newline at end of file diff --git a/bal/__init__.py b/bal/__init__.py index 3093135..ea4dcbb 100644 --- a/bal/__init__.py +++ b/bal/__init__.py @@ -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" diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py index b31d854..0683884 100644 --- a/bal/core/plugin_base.py +++ b/bal/core/plugin_base.py @@ -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 = { diff --git a/bal/gui/qt/plugin.py b/bal/gui/qt/plugin.py index 6c39420..9a18490 100644 --- a/bal/gui/qt/plugin.py +++ b/bal/gui/qt/plugin.py @@ -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 diff --git a/bal/manifest.json b/bal/manifest.json index 131986b..521cf59 100644 --- a/bal/manifest.json +++ b/bal/manifest.json @@ -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",