Commit Graph

31 Commits

Author SHA1 Message Date
kaibot
8a6cfc13cf feat: add per-setting reset button (↺) to each row in settings dialog 2026-06-30 02:50:32 -04:00
kaibot
f574472dcf refactor: extract BalCalendarButton common widget + basic-mode default app 2026-06-30 02:43:05 -04:00
kaibot
02cda3513b feat: add calendar button with dropdown menu to build-will dialog + calendar app setting (advanced mode) 2026-06-30 02:27:50 -04:00
kaibot
6d568bf304 Fix: NoneType error in normalize_will when others_inputs is None
When deleting old transactions and clicking Check, task_phase1 would fail
with 'NoneType object has no attribute get' error in WillItem.normalize_locktime.

Root cause: In Will.normalize_will (will.py:150), the parameter others_inputs
defaults to None. At line 151, a local variable 'others_input' is created with
a safe default value (empty dict). However, lines 179 and 184 still used the
original 'others_inputs' parameter instead of the safe local variable, causing
the error when calling .get() on None.

Fix: Use the safe 'others_input' variable instead of the raw parameter in
lines 179 and 184.

Also added:
- Traceback logging to on_error_phase1 so future errors include full call stack
- Debug logging in update_all to log willitems state before processing
- Comprehensive test (test_e5_build_with_real_wallet_heirs_and_utxos) that
  reproduces the exact scenario from the user's karen7 wallet
2026-06-29 12:03:24 -04:00
kaibot
aa971b8759 fix: distinguish empty response from network error in advanced mode
Show 'No active will-executor servers for {chain}' when the server
responds with no data, and 'Could not reach the configured welist
server' only for actual connection errors.
2026-06-29 01:13:38 -04:00
kaibot
41803139c6 feat: move User Type above advanced-only settings, improve dialog sizing
- Move User Type combo to row 5 (before Number of reminders)
- Use QLayout.SizeConstraint.SetFixedSize so dialog auto-resizes on mode switch
- Hide event summary, description and reminders count in basic mode, use defaults
- Increase Event Description height to 3 lines
- Set minimum width on line edits for proper dialog sizing in advanced mode
2026-06-29 01:07:22 -04:00
kaibot
ff00097af4 docs: update User Type and welist server help texts
User Type help now says BASIC is safe for most users and lists
welist server URL config + always-editable fields as advanced features.
Welist server help text updated to 'Only available in ADVANCED mode'.
2026-06-29 00:29:17 -04:00
kaibot
7a58c69533 feat: add configurable welist server URL (advanced mode only)
- Add NoServersForChainError(chain, url, reason) with error details
- Expose WELIST_SERVER setting in plugin dialog, hidden in basic mode
- Basic mode uses factory default; advanced mode uses configured URL only
- Show descriptive error with URL and reason when advanced-mode fetch fails
2026-06-29 00:18:42 -04:00
kaibot
7004953ce2 fix: keep will-settings fields editable in advanced mode
In advanced mode the locktime, check-alive and fees fields should always
be editable regardless of the EDITABLE_DATES setting. Only basic mode
respects that setting.
2026-06-29 00:01:25 -04:00
kaibot
b8e6c240be feat(willexecutors): differentiate empty server response from network error
Raise NoServersForChainError when the welist server responds but returns
no data for the requested chain, showing a chain-specific message instead
of the generic network error.
2026-06-28 23:56:30 -04:00
donkey-ai
33e8a7533d v0.4.8: seven UX fixes for the BAL inheritance plugin
- #04: Raw/Date selector now reappears on the WILL/HEIR tabs when switching to
  ADVANCED (and when a raw value is pushed from the wizard), keeping the current
  value/editor. New BalTimeEditWidget.apply_user_type_visibility() wired into
  WillSettingsWidget for both the locktime and check-alive boxes.
- #3: Building Will report window minimum height 500 -> 450 px.
- #5: 'User Type' setting moved to the bottom, above 'Rebroadcast transactions'.
- #6: enabling ADVANCED now requires typing 'at My Risk' (case-insensitive);
  wrong phrase or cancel reverts to BASIC. QInputDialog added to common import.
- #7a: on CHECK with an emptied wallet, an extra reassuring line is shown on
  'Checking your will': 'Inheritance already executed (on blockchain)' in GREEN
  (CONFIRMED) or 'Inheritance in mempool (waiting confirmation)' in ORANGE
  (MEMPOOL). New _executed_inheritance_status() helper.
- #7b: 'Balance is too low, or CheckAlive is in the past. Skipped' (space added)
  recoloured to ORANGE instead of red.
- Reset button renamed 'Reset setting' -> 'Reset to Default Setting'.
- Added tests/test_group_h_v048.py (8 tests). Version 0.4.7 -> 0.4.8.
  Full suite: 266 passed; ruff clean; CHANGELOG #24; memory updated (#04 done).
2026-06-28 23:02:40 -04:00
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
donkey-ai
5b707e647b feat(bal): UI batch v0.3.9 + expired-will invalidate regression fix
UI batch (TASK A/B/C/D):
- (A) Clearer 'Will expired' message: shortened will id (8+8 chars) and
  readable UTC date instead of raw UNIX timestamp; shown in WARNING colour
  (orange) instead of ERROR (red) in the wizard, as it is an expected step.
- (A2) Split the orange message onto two lines via <br> (rendered as HTML by
  msg_warning) to avoid an overly long single line.
- (B) History tab labels (text only): inheritance txs -> 'BAL Inheritance
  transaction'; invalidate tx -> 'BAL Invalidate transaction'. Colours left
  to Electrum defaults.
- (C) New 'No will-executor TX' checkbox in plugin settings, bound to the
  existing NO_WILLEXECUTOR config (default ON, kept in sync with the wizard),
  with help text, and included in the settings reset action.
- (D) Wizard button label 'Create your will' -> 'Build Your Will'.

Regression fix (A3):
- When an heir was added to an already-expired will via the wizard, the
  automatic invalidation no longer triggered. Root cause: the inner
  check_will() after build_will() raised WillExpiredException and the handler
  returned (False, None), which never invalidated.
- Auto-opening the invalidation tx window from the closing wizard proved
  unreliable (the window ended up behind the main wallet on some window
  managers; a re-check loop could ask to invalidate repeatedly before the tx
  reached the mempool). The robust fix: close the wizard and show a clear
  instruction popup guiding the user to run 'Tools -> Invalidate' and then
  press 'Check' to finish the will. Reasoning documented in the code.

Version bumped to 0.3.9 (manifest.json, __init__.py, plugin_base.py, VERSION).
CHANGELOG entry #15 updated. Verified: py_compile OK, ruff clean (no new
errors), full test suite 239 passed.
2026-06-28 23:02:11 -04:00
donkey-ai
ce3a463a5e docs(bal): rewrite CHECK ALIVE help text (DATA/RAW modes) + fix two typos (v0.3.8)
Rewrite the CHECK ALIVE help popup (ThresholdTimeWidget.help_text) to explain
both modes, keeping the <b>CHECK ALIVE</b> title bold:
- DATA mode: set the check-alive date; on wallet open, if the date has passed
  the plugin asks whether to postpone the inheritance.
- RAW mode: when less than this time is missing, ask to invalidate; failing to
  invalidate delivers the transactions to the heirs; kept the d/y suffix help.

Fix two typos within this help text: 'less then' -> 'less than',
'currrent' -> 'current'.

Bump version to 0.3.8 across the four version files. CHANGELOG entry #14.
2026-06-28 23:02:03 -04:00
donkey-ai
50129d3b77 feat(bal): export calendar .ics as separate reminder events (v0.3.7)
- open_or_save_calendar(): emit one VEVENT per reminder instead of a single
  event with VALARM blocks. Dates come from compute_reminder_offsets(), spread
  across the check-alive period, with the LAST event one day before the
  locktime. Unique UID per event (bal-<wallet>-<offset>d) and numbered
  summaries ' (reminder N/total)'.
- Removed the now-unused create_alarms() method (no more VALARM).
- Default save filename changed from will_event.ics to BAL_will_event.ics.
- common.py: added timedelta to the datetime import.
- plugin_base.py: updated the NUM_REMINDERS comment for the new behaviour.
- tests: replaced the VALARM E1 test with test_e1_build_separate_events_for_giovanna.
- Bumped version to 0.3.7 (4 files) and added CHANGELOG entry #13.

Tests: 239 passed. ruff: no new errors.
2026-06-28 23:01:54 -04:00
donkey-ai
dd2e160a17 fix(bal): black bar on cancelled invalidation + editable fee follows dates (v0.3.6)
Two GUI fixes bundled together (v0.3.6):

- Fix #10 (black bar): in dialogs.py on_success_phase1, the cancelled
  invalidation branch no longer calls the blocking self.wait(3)+self.close().
  wait() uses time.sleep() and runs in the GUI thread, freezing the UI so the
  'Building Will' dialog could not repaint the area it just resized, leaving a
  black rectangle at the bottom. It now shows 'Aborted' plus the existing
  non-blocking Close button (_add_close_button), consistent with the other
  end-of-flow branches.

- Fix #11 (editable fee): in widgets.py WillSettingsWidget.apply_editable_dates,
  the fee widget (baltx_fees) now follows the same 'Editable dates' setting as
  the locktime/threshold widgets (set_read_only(not editable_dates)) instead of
  being forced read-only. With the setting ticked, dates AND fee are editable
  outside the wizard; unticked, all three are read-only. Default stays OFF.

Version bumped 0.3.5 -> 0.3.6 (manifest.json, __init__.py, plugin_base.py,
VERSION). CHANGELOG: entries #10 and #11. ruff clean; full suite 239 passed.
2026-06-28 23:01:38 -04:00
donkey-ai
b03251e864 feat(bal): Group D / D1 - configurable distributed calendar reminders + save-only .ics
D1 - number of reminders (default 3, max 5):
  - new NUM_REMINDERS config in plugin_base.py
  - new BalSpinBox widget bound to a BalConfig
  - new pure helper compute_reminder_offsets(days, count): reminders spread
    uniformly across the check-alive period, always before the deadline
    (offset >= 1), at most one per available day, de-duplicated, earliest
    first (e.g. (30,3)->[30,16,1], (2,3)->[2,1], (1,3)->[1], (0,3)->[])
  - create_alarms() rewritten to use it and emit one VALARM per offset with a
    DESCRIPTION reminder text
  - 'Number of reminders' spin box (range 1..5) added to the settings dialog
    and to the Reset list

D1b - save-only .ics (ask where, default to Desktop):
  - BalCalendar.desktop_dir() helper (~/Desktop with home fallback)
  - open_or_save_calendar() no longer opens the file with a calendar app; it
    always shows a 'save as' dialog (.ics filter, default will_event.ics,
    starting on the Desktop) and copies the file there, then confirms.
    Identical behaviour on Windows/Linux/macOS. save_to_cwd -> save_ics_to.

Follow-up: removed the now-unused 'Calendar App' field from the settings
  dialog (and from the Reset list). CALENDAR_APP config and
  open_with_default_app left in place (unused) to avoid unrelated changes.

D2 intentionally skipped (per user request).

tests/test_group_d_alarms.py: 7 new tests (NUM_REMINDERS default/change and
  all distribution rules). Full suite: 217 passed.
2026-06-28 23:00:31 -04:00
donkey-ai
9d2cbc2814 feat(bal): Group C settings-dialog improvements (editable dates, narrower RAW box, warning/reset/support, tooltips, bold locktime)
C2 - 'Editable dates' option (default OFF):
  - new EDITABLE_DATES config; checkbox in settings dialog
  - WillSettingsWidget.apply_editable_dates() re-locks/unlocks the
    delivery-time and check-alive fields, called from update_all() so the
    toggle takes effect immediately (same mechanism as Hide Invalidated)
  - EDITABLE_DATES included in the Reset-to-defaults list

C3 - RAW date box narrowed to ~1/3 (input box 6 chars); the trailing
  label that shows the computed absolute date kept at 10 chars (fixed a
  truncation regression)

C4 - settings dialog:
  (a) bold red warning at the top
  (b) 'Reset setting' button restoring all 7 dialog settings to defaults
      (single source of truth: BalConfig.default), not touching wills
  (c) bold 'Support: bitcoin-after.life' link (opens via webopen)

C5 - tooltips: calendar 'Export reminder dates to your calendar (.ics)',
  fee field 'Mining fee rate in sat/vByte used for the will transactions',
  fee '丰' icon 'Miner fee, click for more information' (tooltip font scoped
  to QPushButton so it matches the other tooltips)

C6 - Locktime column rendered in bold in the will list

tests/test_group_c_settings.py: 4 new tests (EDITABLE_DATES default/toggle,
  reset restores all dialog settings incl. EDITABLE_DATES, reset leaves
  unrelated config untouched). Full suite: 210 passed.
2026-06-28 23:00:30 -04:00
donkey-ai
dc166d04ff feat(bal): Group A (timestamps + statuses + anticipate docs) and Group B (auto-sign) v0.3.4
Bump plugin version to 0.3.4 (manifest, __init__, plugin_base, VERSION).

GROUP A
- A1: remove block-height locktimes; the plugin now uses UNIX timestamps
  only. The NLOCKTIME_BLOCKHEIGHT_MAX guard is kept on purpose (it forces
  every locktime to be a timestamp). chk_locktime is now 2-arg; int_locktime
  and anticipate_locktime no longer accept blocks; RAW input only accepts d/y.
  Two now-dormant configs (LOCKTIME_BLOCKS, LOCKTIMEDELTA_BLOCKS) are kept with
  comments to avoid touching persisted keys.
- A2: rename PENDING -> MEMPOOL everywhere (label 'Mempool', yellow #ffce30);
  add new UPDATED status; ANTICIPATED & UPDATED keep VALID; documented
  set_status rules; backward-compat migration (old PENDING -> MEMPOOL).
- A3: clarify that anticipating to a future date only rebuilds (never
  invalidates), while only a past locktime invalidates (WillExpired). Code was
  already correct; only the comment and docs were fixed.

Colour follow-up: UPDATED lightened from #800080 to #b266b2 (more readable),
updated in theme.py, docs and the theme test.

GROUP B
- B1: verified the 'Create your will' button already opens the guided wizard
  (no code change needed).
- B2: new persisted AUTO_SIGN setting (default ON) with an 'Auto-sign on Check'
  checkbox in the settings dialog. When enabled, Check signs and broadcasts
  automatically; the wallet password is requested only for encrypted wallets.

B2 follow-up (fixes reported after testing):
- Remove the duplicate sign/broadcast cycle in lists.check(); build_will_task()
  already signs and broadcasts.
- Suppress the manual 'press Sign/Broadcast' hint and its popup when AUTO_SIGN
  is ON (kept when OFF).
- Make broadcast one-shot: removed the retry flag and the Exception('retry');
  failed will-executors stay PUSH_FAIL and are skipped (no endless retry).
  PUSHED transactions are already excluded from re-collection.

Docs: inheritance-options.md/.html and inheritance-flow.svg updated to v0.3.4.
Tests: 206 passing (new test_anticipate_manual_locktime, test_anticipate_past_locktime,
test_group_b_auto_sign; updated core/util, core/will_extra, gui/theme, gui/widgets).
CHANGELOG.md added with one numbered entry per task.
2026-06-28 23:00:23 -04:00
GenSpark AI Developer
30bab62247 UI polish and signed-tx colour fix (v0.3.3)
Fix and refine the BAL plugin GUI without changing business logic:

core/will.py: restore the PUSHED requirement in needs_server_check so a
signed-but-not-broadcast will is no longer server-queried and therefore
stays blue (COMPLETE) instead of turning red (CHECK_FAIL). This matches the
original Gitea check() condition.

gui/qt/widgets.py: WillSettingsWidget vertical layout now caps every row to
the widest date-row width and left-aligns them; the leading icons keep their
original HelpButton width.

gui/qt/lists.py + gui/qt/common.py: the wizard toolbar button now shows a
28x28 icon plus a bold 'Create your will' caption (QSize imported).

gui/qt/dialogs.py (BalBuildWillDialog):
- closing summary row labelled 'All done: Ok' with a blank separator above it;
- 'checking variables' capitalised to 'Checking variables' (redundant trailing
  colon dropped);
- final auto-closing countdown replaced by an explicit right-aligned 'Close'
  button; intermediate technical pauses kept; next-steps popup preserved.

gui/qt/window.py + core/plugin_base.py: guide show_message on build, and
sync_hide_filters() in update_all so hide flags refresh immediately.

tests: test_needs_server_check updated; added offscreen preview helpers.
Version bumped to 0.3.3. 186 tests pass; ruff clean (baseline only).
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
365824767b 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.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
e477c5aa5b fix(will): prevent double invalidation when postponing a signed will
When postponing the delivery time of an already signed/sent will, the user
was asked to sign the on-chain invalidation transaction twice before the new
(postponed) will could be built.

Root cause: after the invalidation tx was broadcast, on_success_invalidate
restarted task_phase1 to rebuild the will, but the old will items were still
marked COMPLETE/PUSHED with their original tx.locktime (the on-chain
invalidation did not update the in-memory status). The postpone check therefore
fired WillPostponedException a second time, requesting another invalidation.

Fix:
- Add Will.mark_invalidated_by_tx(will, tx): marks INVALIDATED every valid will
  item that spends a prevout consumed by the just-broadcast invalidation tx.
  Setting INVALIDATED clears the VALID flag, removing those items from
  only_valid_list so the postpone/expire check no longer fires.
- Call it from loop_broadcast_invalidating after a successful broadcast (txid
  obtained) and persist via save_willitems. On the phase-1 restart the old will
  is no longer VALID, so the will is rebuilt directly: a single invalidation
  signature followed by the new will.

Tests: add test_will_mark_invalidated_by_tx and
test_will_mark_invalidated_by_tx_no_match plus the WillPostponedException
hierarchy assertion. 184 tests pass; smoke and external-zip OK; ruff clean.

Bump version to 0.3.1.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
343046ed34 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-28 22:51:28 -04:00
GenSpark AI Developer
4e027d9e8b fix(qt): auto-close Plugins manager, read-only field styling, RLock-safe heirs persistence
GUI / plugin lifecycle:
- Auto-close Electrum's native 'Electrum Plugins' manager dialog after the
  BAL plugin is hot-enabled. Electrum 4.7.x no longer calls the old init_qt
  hook, so the close is now triggered from the create_status_bar, init_menubar
  and load_wallet hooks (fired when reload_windows() recreates the window).
- Robust dialog matching (isinstance / class name / localized window title) to
  cope with zipimport module-identity mismatches.
- Robust dismissal of the modal dialog (reject()/done()/close()) with a retry
  schedule [400, 800, 1500] ms; if it still cannot be closed, fall back to
  bringing it to the front (showNormal/raise_/activateWindow) so it never
  lingers hidden in the background. Counting only visible top-levels avoids
  treating an already-closed dialog as still open.

Read-only field styling:
- Paint the locked Delivery time / Check Alive date editors and the mining-fee
  spinbox with a light-grey background (#f0f0f0) so the user can see at a glance
  that they are not editable outside the 'Build your will' wizard; the styling
  is cleared when the fields are made editable again.

Pickle/RLock crash on 'Build will':
- heirs.save() now sanitises the heirs mapping via _json_safe() before handing
  it to json_db.put(), which deep-copies the value. A live runtime object
  (holding a threading.RLock) slipping into an heir value previously raised
  'TypeError: cannot pickle _thread.RLock object' and aborted the task; such
  values are now coerced to str and logged with their path.
- init_heirs_to_locktime() coerces the locktime to a plain serializable scalar.
- log_error() now accepts both a sys.exc_info() triple and a single exception
  instance, fixing the secondary 'TypeError object is not subscriptable' that
  masked the real error.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
13259e881d feat(net): parallel Check + timeout counters + GUI tooltips/order
Networking (anti-freeze):
- Add check_transactions_parallel: pressing "Check" now contacts will-executors
  concurrently with a fast-fail timeout (CHECK_TIMEOUT=8, 1 retry) and a global
  deadline (CHECK_GLOBAL_DEADLINE=30), so a single dead server no longer freezes
  the "checking transaction" dialog for ~140s (old default 10s x 10 retries).
- check_transaction now accepts timeout/max_retries/retry_sleep kwargs.
- Rewrite BalWindow.check_transactions_task to use the parallel helper with live
  progress + an elapsed-time counter "Checking transactions: 2/5 (4s / 30s)".

Reliable elapsed-time counters (Xs / DEADLINEs):
- Replace the unreliable raw heartbeat thread (whose pyqtSignal emissions were
  not marshalled and never repainted) with an on_tick callback driven from the
  CALLING thread in push/ping/check parallel helpers.
- Show the maximum wait too (e.g. "3s / 30s") so the user knows when the
  operation will give up, in the wizard Broadcasting, Ping and Check dialogs.
- Expose networking constants (PUSH_*/CHECK_*/DEFAULT_TIMEOUT) as Willexecutors
  class attributes for a single source of truth in the GUI.

GUI:
- Add hover tooltips: Wizard ("Wizard - Build your will"), Delivery time (truck),
  Check Alive (siren), Calendar, Check (refresh).
- Reorder the Will toolbar to: Wizard | Delivery time | Check Alive | Calendar |
  Check; tighten layout margins so it all fits the Will window.

Tests:
- parallel_ping_test.py: add coverage for check_transactions_parallel (parallel
  timing, global deadline, on_tick from the calling thread) and static checks
  that check_transactions_task/loop_push use the parallel helpers + on_tick
  counter with the 'Xs / Ns' format.

Verified: ruff (0 new issues), 182 official tests pass, parallel/smoke/gui_fixes/
windows_overflow/external_zip tests pass.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
fbe94506f8 perf(wizard): parallelize Will-Executor broadcast in Building Will wizard
The Building Will wizard (BalBuildWillDialog.loop_push) still broadcast the
will to will-executors sequentially -- a for-loop calling
push_transactions_to_willexecutor one server at a time. This is the slow
"Broadcasting your will to executors: Trasmissione" step the user saw: a
slow/dead server blocked the whole wizard, just like the non-wizard path did
before it was parallelized.

Rewrite loop_push to use Willexecutors.push_transactions_parallel (the same
helper already used by window.push_transactions_to_willexecutors):
- Pre-filter to the user-selected will-executors only.
- Push to all selected servers concurrently (ThreadPoolExecutor); each server
  keeps its own retry behaviour, but a slow/dead server no longer blocks the
  others. Total time ~= slowest server, not the sum.
- on_each callback does thread-safe book-keeping + UI update via msg_edit_row
  (which emits a pyqtSignal marshalled to the GUI thread).
- 'already present' servers are collected and their stored tx verified
  sequentially afterwards (original check_transaction logic preserved).
- Preserve the retry flag and the _stopping cancellation checks.

tests/parallel_ping_test.py: add a static check asserting loop_push uses
push_transactions_parallel and no longer contains the sequential push loop.

Tests: 182 official + smoke/overflow/gui_fixes/parallel/external_zip all pass.
ruff: no new issues; new code is PEP8-compliant.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
f28f1502e2 fix(gui): restore BAL status-bar icon (bottom-right) + open settings on click
Regression: create_status_bar had been turned into a no-op while chasing the
"condensed menu/tabs" bug. The real cause of that bug was a Windows
OverflowError (year 2038), already fixed separately -- so the no-op wrongly
removed the BAL icon from the status bar.

Restore the original (Gitea) behaviour:
- Build a StatusBarButton with the bal32x32 icon and add it via
  addPermanentWidget, so the icon shows the plugin is installed.
- Clicking the icon opens settings_dialog (quick access to plugin settings).
- Track buttons in self._statusbar_buttons keyed by id(sb.window()); remove the
  stale button before creating a new one to avoid a duplicated icon on restart
  / wallet switch.

Also:
- import StatusBarButton from electrum.gui.qt.main_window; ensure
  read_QIcon_from_bytes is imported; init self._statusbar_buttons in __init__.
- Update tests/gui_fixes_test.py: the regression check now asserts the icon IS
  added (StatusBarButton + addPermanentWidget + settings_dialog +
  _statusbar_buttons book-keeping), instead of the previous wrong no-op check.

Tests: 182 official tests pass; smoke/windows_overflow/parallel/external_zip OK.
Note: from now on all code and code-comments are in English.
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
03985a2566 feat(net): invio/ping/download verso Will-Executor in parallelo (anti-freeze)
Problema: i server Will-Executor venivano contattati in sequenza e, su
timeout, send_request riprovava 10x con sleep 3s (~130s per server morto).
Un solo server irraggiungibile bloccava l'intera operazione (impallamento UI).

Soluzione:
- ThreadPoolExecutor: ping e push ora in parallelo (tempo ~= server piu' lento,
  non la somma). Un server morto non blocca piu' gli altri.
- Fast-fail per operazioni interattive (ping/info/download): max_retries=0,
  niente retry-storm.
- Feedback live: callback on_each() aggiorna il dialog server-per-server
  (thread-safe via pyqtSignal di BalWaitingDialog.update).
- Push transazioni: parallelo ma con retry per-server mantenuti (no perdita tx).

File:
- bal/core/willexecutors.py: send_request(+max_retries,+retry_sleep),
  get_info_task fast-fail, NEW ping_servers_parallel(), push_transactions_parallel(),
  DEFAULT_TIMEOUT=5.
- bal/gui/qt/window.py: ping_willexecutors_task + push_transactions_to_willexecutors
  riscritti su helper paralleli con feedback live; fetch_will_executors_list fast-fail.
- bal/core/util.py: BUGFIX get_value_amount usava in_output (bool) invece di
  din_output (tupla) -> TypeError. Scoperto dai test ufficiali Gitea.

Test (contro il codice refactor):
- pytest tests/ ufficiali: 117 core + 65 gui = 182 passed.
- smoke/external_zip/windows_overflow/gui_fixes: OK.
- parallel_ping_test (nuovo): 0.50s per 8 server vs ~4.00s sequenziale.
- ruff: nessun nuovo problema introdotto (codice nuovo PEP8-compliant).

Aggiunti i test ufficiali del repo Gitea + REPORT_NETWORKING_PARALLELO.md.
2026-06-28 22:51:28 -04:00
genspark-ai-developer[bot]
b1c8bba9e9 fix: crash GUI su Windows (OverflowError anno 2038) — schede/menu BAL rotti (#3)
* fix(gui): voci di menu BAL duplicate/condensate dopo riavvio o cambio wallet

Sintomo (Windows 11): dopo aver riavviato Electrum o cambiato wallet, le
schede Will/Heirs sparivano dalla tab bar e dal menu, e compariva una voce
di menu condensata/illeggibile (icona + testo sovrapposti) sotto il logo di
Electrum, accanto a 'Portafogli'.

Causa: init_menubar_tools veniva eseguito DUE volte sulla stessa finestra.
Con il plugin gia abilitato, al riavvio Electrum invoca sia l'hook
init_menubar sia il percorso di init a caldo (init_qt -> _setup_window),
entrambi chiamano init_menubar_tools -> addTab/addAction duplicati.
Nell'originale init_qt faceva return (chiedendo il riavvio) e quindi i menu
venivano creati una sola volta; rimuovendo quel return (fix B3) e' emersa la
doppia inizializzazione.

Fix:
- BalWindow._menubar_initialized: guardia di idempotenza.
- init_menubar_tools: se gia inizializzato, esce subito (niente duplicati).
- on_close: resetta il flag dopo aver rimosso tab/azioni, cosi la stessa
  finestra puo essere riusata per un altro wallet.
- tests/gui_fixes_test.py: regressione che verifica la guardia in __init__,
  init_menubar_tools e on_close.

Logica di business invariata (nessuna modifica a bal/core/*).

* fix(gui): ripristina create_status_bar come no-op (come originale)

L'elemento di menu condensato/illeggibile sotto il logo di Electrum era
causato dal StatusBarButton aggiunto da create_status_bar.

Nell'originale Gitea questo hook aveva un 'return' subito dopo il log, PRIMA
di costruire il bottone: era quindi disabilitato di proposito. Durante la
pulizia del 'dead code' nel refactoring quel return era stato rimosso,
riattivando la creazione del bottone -> elemento icona+testo renderizzato
nel punto sbagliato dopo riavvio/cambio wallet.

Fix: create_status_bar torna a essere un no-op (return), fedele all'originale.
Le impostazioni restano raggiungibili da Strumenti -> Plugin.

Regressione: gui_fixes_test verifica che create_status_bar non chiami
addPermanentWidget.

* fix(core): OverflowError su Windows (anno 2038) che rompeva tab/menu BAL

CAUSA VERA (dal log Electrum dell'utente, Windows 11):

  OverflowError: Python int too large to convert to C int
    window.py __init__ -> create_heirs_tab -> WillSettingsWidget
    -> on_locktime_change -> BalTimestamp.to_date
    -> datetime.fromtimestamp(NLOCKTIME_MAX)

Su Windows time_t e' a 32 bit, quindi datetime.fromtimestamp() solleva
OverflowError per qualsiasi timestamp oltre il 2038 (es. NLOCKTIME_MAX =
2**32-1 = 4294967295, usato come locktime di default/sentinella). Su Linux
64-bit la stessa chiamata funziona: per questo il bug si vedeva solo su
Windows e i test su Linux non lo intercettavano.

L'eccezione interrompeva BalWindow.__init__ durante init_menubar/load_wallet,
lasciando le schede Will/Heirs e la voce di menu a meta' costruzione ->
l'elemento grafico condensato/illeggibile sotto il logo di Electrum.

FIX (comportamento invariato per tutti i valori normali):
- BalTimestamp._safe_fromtimestamp(): datetime.fromtimestamp con clamp a
  INT32_MAX in caso di OverflowError/OSError/ValueError, esattamente come la
  funzione get_max_allowed_timestamp() dell'originale (Electrum issue #6170).
- Usato in to_date / to_timestamp / __str__ / __repr__ di BalTimestamp.
- widgets.py set_value: usa il converter sicuro.
- util.py timestamp_minus: stessa protezione inline con clamp a INT32_MAX.

I valori entro il 2038 (date assolute normali, durate relative come 90d/5y)
producono lo stesso identico risultato di prima.

TEST: tests/windows_overflow_test.py riproduce il limite 32-bit di Windows
(monkeypatch di datetime.fromtimestamp) e dimostra che senza il fix si ottiene
lo stesso OverflowError del log, mentre col fix passa. Verificato anche che il
test FALLISCE senza il fix.

* docs(it): documenta il fix OverflowError Windows (anno 2038) nel changelog

Aggiunge la sezione §13 al CHANGELOG_REFACTOR.md che descrive:
- sintomo (schede/menu rotti su Windows dopo riavvio/cambio wallet)
- causa vera dal log (datetime.fromtimestamp(NLOCKTIME_MAX) -> OverflowError
  su time_t 32-bit di Windows)
- fix con _safe_fromtimestamp (clamp a INT32_MAX, come Electrum #6170)
- test di regressione windows_overflow_test.py
Aggiornata anche la cronologia (§12) con PR #3.

---------

Co-authored-by: GenSpark AI Developer <ai@genspark.dev>
2026-06-28 22:51:28 -04:00
genspark-ai-developer[bot]
c8a9cbfc0a fix(gui): window z-order + lifecycle (B1-B10) (#2)
* fix(gui): correct window z-order and lifecycle bugs (B1-B10)

Sintomi risolti:
- S1: le finestre del plugin sparivano dietro Electrum
- S2: alcuni meccanismi funzionavano solo dopo chiusura+pulizia di Electrum

La logica di business resta BYTE-IDENTICA (nessuna modifica a bal/core/*):
cambiano solo parent, modalità, ciclo di vita, cleanup e presentazione.

Nuovo modulo bal/gui/qt/window_utils.py con helper centralizzati:
- top_level_of, bring_to_front, stop_thread, show_modal, show_on_top

Fix per bug:
- B1: self.parent -> self._bal_parent (dialogs/lists/widgets); parent = top_level_of(parent)
- B2/B9: .show() -> show_on_top()/show_modal()/bring_to_front()
- B3: init a caldo _setup_window() replica load_wallet (niente 'restart Electrum')
- B4: chiave finestra stabile _window_key() = id(window)
- B5: on_close riscritto (no except:pass, log per-step, reset stato)
- B6: BalBlockingWaitingDialog ripristina processEvents()
- B7/B8: closeEvent/hideEvent -> stop_thread() (stop+wait) + super()
- B10: uso di window.tools_menu (no ricerca per titolo localizzato '&Tools')

Test: smoke + gui_fixes (regressione B1-B10) + external_zip tutti verdi.
Doc aggiornata: DIAGNOSI_GUI.md marca B1-B10 come FIXED.

* fix(gui): do not kill task thread on dialog close (download list regression)

The B7/B8 change added stop_thread() to BalDialog.closeEvent/hideEvent.
But Electrum's TaskThread.on_done runs cb_done (often self.accept, which
closes the waiting dialog) BEFORE cb_result (on_success, which updates the
will-executor list). Stopping/joining the thread inside closeEvent therefore
tore the thread down before on_success ran, silently dropping the downloaded
will-executor list ('Download List' appeared to do nothing).

Restore the original safe behavior: the base BalDialog no longer stops the
thread on close/hide (matching the original plugin, which deliberately left
this commented out). Long-lived dialogs that own a thread still stop it
explicitly in their own handlers.

Adds a regression test asserting BalDialog.closeEvent/hideEvent never call
stop_thread.

* fix(gui): restore modal exec for waiting dialog + surface download failures

Two changes to fix 'Download List' doing nothing:

1) BalWaitingDialog.exe() now keeps the original application-modal exec()
   (only adding raise/activate for visibility). The earlier switch to
   window-modal could interfere with how the TaskThread result (on_success,
   which populates the will-executor list) is delivered via a queued signal
   while the modal loop is spinning.

2) BalWindow.download_list now logs how many entries were received and, when
   the result is empty (the core download_list swallows errors and returns {}),
   shows a warning to the user instead of failing silently. This makes any
   future network/parse failure visible in the Electrum log and to the user.

Business logic in bal/core/* is unchanged.

* fix(gui): show the real download error reason in the warning popup

When 'Download List' fails, the core download_list returns {} and the cause
was only visible in the Electrum log (which is hard to capture). The GUI now
re-issues the same raw request when the result is empty and shows the actual
exception/reason in the warning popup (e.g. SSL error, timeout, empty server
response). Pure GUI-side diagnostics; bal/core/* logic unchanged.

* fix(gui): download will-executor list synchronously like the original

ROOT CAUSE: the original plugin's 'Download List' button called
Willexecutors.download_list() DIRECTLY on the GUI thread
(qt.py: WillExecutorWidget.download_list). The refactor instead routed the
button through BalWaitingDialog + TaskThread. Electrum's
Network.send_http_on_proxy behaves differently depending on the calling
thread, and on the user's setup the TaskThread path timed out ('No response
from the server'), while the original direct call worked fine.

FIX: WillExecutorWidget.download_list now performs the same direct,
synchronous download as the original, updates and saves the list, and shows a
warning only on genuine failure. The wizard download path (which the original
also ran via TaskThread) is left unchanged.

Compared against upstream original source (kaibot/bal-electrum-plugin):
send_request, handle_response, download_list core logic are byte-identical;
only the GUI call site is restored to the original behaviour.

* diag(gui): detailed download diagnostics + hardcoded-URL fallback

The will-executor download still times out on the user's setup even with the
direct (original-style) GUI-thread call, and the URL/request are byte-identical
to the working original. To pinpoint the real cause, the Download List button
now:
- logs whether a Network instance is present;
- tries the configured WELIST_SERVER URL AND falls back to the original
  hardcoded https://welist.bitcoin-after.life/ endpoint (so a stale/bad config
  value can't break it);
- shows the EXACT URL(s) tried and the precise exception per attempt in the
  warning popup instead of a generic timeout message.

bal/core networking remains unchanged.

* diag(gui): add direct-HTTPS control probe to download diagnostics

When the Electrum-network download fails, also run a plain urllib HTTPS GET
(bypassing Electrum's Network/proxy layer) and show its result in the popup.
This distinguishes a real connectivity/DNS/firewall problem from an
Electrum-network-state problem, so we can finally pinpoint why the request
times out only in this build.

* fix(gui): unify wizard + button download on one synchronous path with diagnostics

The 'No response from the server (timeout?)' popup was coming from the WIZARD
download path in window.py (BalWaitingDialog + TaskThread), which was never
switched to the original direct call - only the list button had been.

Both paths now share BalWindow.fetch_will_executors_list(): a direct,
synchronous GUI-thread download (like the original), trying the configured
server then the hardcoded fallback, with full diagnostics (exact URL/error per
attempt + a direct-HTTPS control probe) shown in the failure popup.

This both fixes the wizard timeout and guarantees the same diagnostic popup
('Details (via Electrum network)' + 'Direct connection test') regardless of
which UI entry point is used.

* fix(gui): clean up will-executor download (waiting dialog + simple message)

Root cause of the 'download not working' reports was environmental (the user's
network/ISP was resetting the connection to the IPv6/IPv4 host; a VPN fixes
it), NOT a plugin bug. Final cleanup of the diagnostic code:

- download_list (button + wizard) again uses BalWaitingDialog + TaskThread so
  the GUI is not frozen and shows a 'Downloading will-executors list...' dialog.
- Keep the configured + hardcoded-fallback server URLs and detailed per-attempt
  diagnostics, but write them to the Electrum log only.
- On failure the user now sees a simple English message explaining it is most
  likely a connection/firewall issue (a VPN often helps), instead of a technical
  timeout/exception dump.
- Removed the urllib control probe from the user-facing popup.

bal/core networking unchanged.

---------

Co-authored-by: GenSpark AI Developer <ai@genspark.dev>
2026-06-28 22:51:28 -04:00
GenSpark AI Developer
4198a5145b BAL - Bitcoin After Life Electrum plugin (v0.2.8)
Behavior-preserving refactor of the original BAL plugin with clean separation
of business logic from the PyQt GUI.

Layout:
  bal/core/   GUI-free logic (util, plugin_base, heirs, will, willexecutors)
  bal/gui/qt/ PyQt6 presentation (theme, common, widgets, calendar, dialogs,
              lists, window, plugin)
  bal/qt.py   Qt entry-point shim (works as internal and external zip plugin)
  bal/manifest.json  standard-conforming metadata

Tooling:
  build_zip.py            deterministic, zipimport-friendly archive builder
  tests/smoke_test.py     imports + behavior regression test
  tests/external_zip_test.py  reproduces Electrum's external-zip loading

Targets Electrum 4.7.2 + PyQt6. Logic kept byte-identical where possible.
2026-06-07 09:22:34 +00:00