Files
bal-electrum-plugin/HANDOFF.md
donkey-ai ed83af6be9 docs(bal): add HANDOFF.md to resume the project in a new chat/model
Adds a single handoff document summarizing the mandatory rules (R1-R4,
zip-first), project overview, build/test/release commands, current state
(v0.3.9 released), the pending 'unify invalidate procedure' task with the
user-approved warning popup text, and a key file/line map. Lets any new chat
or AI model resume work without losing context. Documentation only; no plugin
code changed.
2026-06-28 23:02:18 -04:00

11 KiB
Raw Blame History

HANDOFF — BAL Electrum Plugin (Bitcoin After Life)

Purpose of this file: allow ANY new chat / AI model to resume this project WITHOUT losing context. If you are a new assistant, READ THIS FILE FIRST, then read CHANGELOG.md and .agent_memory_tasks.md.

Chat language is Italian, but ALL output (code, comments, UI text, docs, CHANGELOG, commit messages) MUST be in ENGLISH. The user is NOT a programmer.


1. MANDATORY STANDING RULES (apply to EVERY task, never skip)

  • R1 — LANGUAGE: Italian is ONLY for chatting. ALL deliverables in ENGLISH (code, docstrings, comments, UI strings, docs, CHANGELOG, commit messages).
  • R2 — DOCUMENTED CODE: every method/class needs a docstring + explanatory comments. When a non-obvious design choice is made, explain WHY in a comment.
  • R3 — NEVER INVENT: if anything is missing or unclear, STOP and ask the user clear, simple questions (he is not a programmer). Be "100% sure" before acting.
  • R4 — HUMAN CHECKPOINT: before writing/modifying code, show the PLAN and WAIT for the user's explicit "OK".
  • METHOD per task: DISCOVER → PLAN (wait OK) → EXECUTE → VERIFY → ITERATE (max 8 attempts, then declare "UNRESOLVED").
  • LOG: a single CHANGELOG.md in English, one numbered entry per task.
  • ZIP-FIRST: always deliver a test ZIP for the user to try BEFORE committing plugin code. Commit ONLY after explicit user confirmation.
  • Always run ruff + the official test suite before committing/reporting/zipping.

2. PROJECT OVERVIEW

  • Electrum 4.7.2 Qt (PyQt6) inheritance plugin.
  • External zip plugin id: electrum_external_plugins.bal.
  • zipimport caches the plugin → the user MUST fully restart Electrum after installing a new ZIP (always remind him).
  • Repo: Bitcoin-after-life/test (GitHub). Working branch: genspark_ai_developer.
  • Main code lives under bal/. Tests under tests/. Electrum source vendored in electrum-src/ (read-only reference).

Version files (keep ALL FOUR in sync on every release)

  • bal/manifest.json"version"
  • bal/__init__.py__version__
  • bal/core/plugin_base.py__version__ = "..." # AUTOMATICALLY GENERATED DO NOT EDIT
  • bal/VERSION
  • Current released version: 0.3.9

3. BUILD / TEST / RELEASE COMMANDS

Build the ZIP (clear caches first)

cd /home/user/webapp
find bal -name "__pycache__" -type d -exec rm -rf {} + ; find bal -name "*.pyc" -delete
python3 build_zip.py bal-electrum-plugin-vX.Y.Z.zip   # builds 37 files

Run the full test suite (must stay GREEN: 239 passed)

cd /home/user/webapp
QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src python3 -m pytest \
  tests/test_core_*.py tests/test_gui_*.py \
  tests/test_anticipate_past_locktime.py tests/test_anticipate_manual_locktime.py \
  tests/test_group_b_auto_sign.py tests/test_group_c_settings.py \
  tests/test_group_d_alarms.py tests/test_group_e_mock_giovanna7.py -q

Ruff (only PRE-EXISTING noise is acceptable)

cd /home/user/webapp && ruff check bal/<changed files>

Pre-existing warnings that are NOT your fault and can be ignored:

  • F401/F403/F405 star-import noise (from .common import *).
  • F841 at: will.py:151, dialogs.py:615 (except NoHeirsException as e), lists.py:185, lists.py:272.
  • E501 long lines in several pre-existing spots.

Git / PR / Release workflow

  • setup_github_environment first. If git push fails with "Invalid username or token", CALL setup_github_environment AGAIN, then retry.
  • Token for API calls: TOKEN=$(sed -n 's#https://\([^:]*\):\([^@]*\)@.*#\2#p' ~/.git-credentials | head -1)
  • Repo for API: Bitcoin-after-life/test.
  • Releases published so far: v0.3.6, v0.3.7, v0.3.8, v0.3.9 (latest).
  • Attach the ZIP as a release asset via the uploads API.

4. CURRENT STATE (as of v0.3.9, COMMITTED + RELEASED)

v0.3.9 is merged to main (PR #11) and released with the ZIP attached. sha256 of the released ZIP: cd52b6f5e6276fb707ea4bf477a00bd0469dfd88960282fb74c219ee0f5f4292.

What shipped in v0.3.9 (TASK A/B/C/D + regression fix A3):

  • A clearer "Will expired" message: shortened will id (8+8 chars via Will._short_will_id) + readable UTC date (Will._format_locktime) instead of raw UNIX timestamp; shown ORANGE (warning) not RED (error) in the wizard.
  • A2 message split on two lines via <br> (rendered as HTML by msg_warning).
  • B History labels (text only): inheritance tx → BAL Inheritance transaction; invalidate tx → BAL Invalidate transaction. Colours = Electrum defaults (Electrum colours outgoing tx descriptions red by itself, history_list.py:193-196).
  • C "No will-executor TX" checkbox in plugin Settings (plugin.py), bound to existing NO_WILLEXECUTOR config (default ON, line plugin_base.py:193), with help text "Create a will that does not require a Will-executor; it can be saved, for example, on a USB stick, and a copy can be given to the heirs." Included in reset.
  • D wizard button "Create your will" → "Build Your Will" (lists.py:473).
  • A3 regression fix: adding an heir to an expired will via the wizard no longer failed to invalidate. Implemented a "invalidate_classic" signal returned from task_phase1 and handled in on_success_phase1 (dialogs.py) that closes the wizard and shows a popup telling the user to use Tools → Invalidate. NOTE: this popup text is about to be CHANGED — see the pending task below.

5. PENDING TASK (NOT STARTED) — UNIFY THE INVALIDATE PROCEDURE

Problem the user reported

There are currently TWO different "invalidate" procedures, and the user wants ONE identical behaviour for BOTH the CHECK button and the WIZARD.

  • PROCEDURE 1 "classic/manual" = window.py::invalidate_will (line ~695): waiting dialog → "please sign and broadcast" popup → CLASSIC Electrum tx window (Sign/Broadcast buttons) → SETS history label "BAL Invalidate transaction" (line ~704). Used by: Tools→Invalidate menu (lists.py:468→571), a dialog button (dialogs.py:1356), the on-close/postpone paths (window.py:539,576,614). This window opens correctly IN FRONT (user confirmed) because nothing else is closing at the same time.
  • PROCEDURE 2 "automatic" = dialogs.py::invalidate_task (line ~902): password prompt inside the wizard → sign + auto-broadcast (loop_broadcast_invalidating, line ~729) → does NOT set the history label. Used by the CHECK button and the FIRST WillExpiredException handler in task_phase1 (dialogs.py:594 → return None, Will.invalidate_will at ~598, which makes on_success_phase1 see have_to_sign is None → password prompt).

CHECK button flow (important)

lists.py:545 check()BalBuildWillDialog(...).build_will_task()task_phase1on_success_phase1. It is the SAME engine as the wizard.

FINAL REQUIREMENT (user-approved, OPTION A refined)

Make CHECK and WIZARD behave IDENTICALLY for an expired will:

  1. First show a WARNING popup (REMOVE the old "use the top-right menu Tools → Invalidate" wording).
  2. Then AUTOMATICALLY open the CLASSIC Electrum sign window (PROCEDURE 1, window.py::invalidate_will) so the label is set and the user can Sign + Broadcast.
  3. Sequence: warning popup → user clicks OK → classic sign window opens BY ITSELF, IN FRONT.

APPROVED WARNING POPUP TEXT (verbatim, English per R1)

Your will has expired and must be invalidated before it can be rebuilt.
A transaction window will now open:
please SIGN and then BROADCAST it to invalidate your old will.
After the invalidation is confirmed, press the Check button to finish the will.

(The user wrote "SIGN and then BROADCAST" with a double space — normalize to a single space unless he objects.)

Implementation approach (agreed in principle; still needs final PLAN + OK)

  • Route the expired cases (FIRST handler at ~594, the invalidate_classic block, and therefore the CHECK button) through ONE shared helper that: (a) closes the CHECK/wizard dialog FIRST, (b) shows the warning popup, (c) then calls self.bal_window.invalidate_will() (PROCEDURE 1) LAST, so the classic window is the last thing opened and stays in front.
  • Drop the use of PROCEDURE 2 (invalidate_task) for the expired case.
  • KNOWN RISK / why this is delicate: earlier attempts to auto-open the classic window while the wizard was closing put it BEHIND the main wallet window on the user's machine (Windows focus/stacking). The fix is to make sure NOTHING closes AFTER the classic window opens (close the dialog first, open the tx window last). Tools → Invalidate works perfectly precisely because no other window is closing.
  • The user has hinted he may add MORE requirements before this is implemented, so CONFIRM the full scope before coding.

Status: WAITING. Do NOT code yet. Build full PLAN → wait OK (R4) → zip-first.


6. KEY FILE / LINE REFERENCES (verify line numbers, they drift)

  • bal/core/will.py
    • check_will() order (line ~561): check_invalidatedcheck_will_expired (raises WillExpiredException) → search_rai (raises HeirNotFoundException).
    • invalidate_will() static (line ~394) builds the invalidation PartialTransaction.
    • _short_will_id / _format_locktime helpers + the expired message (with <br>).
  • bal/gui/qt/dialogs.py
    • BalBuildWillDialog is the CHECK + wizard engine.
    • build_will_task() (~530) starts task_phase1.
    • task_phase1() (~542): first check_will(); first WillExpiredException handler (~594) → return None, Will.invalidate_will(...); NotCompleteWill/ HeirNotFoundhave_to_build; inner check_will(); inner WillExpiredException (~659) → currently returns "invalidate_classic", None.
    • on_success_phase1() (~924): unpacks (have_to_sign, tx). If have_to_sign == "invalidate_classic" → shows popup. If have_to_sign is None → password prompt "Invalidate your old will" → invalidate_task (PROCEDURE 2).
    • invalidate_task() (~902) + loop_broadcast_invalidating() (~729): PROCEDURE 2.
    • QTimer is available via from .common import * (defined in common.py:53).
  • bal/gui/qt/window.py
    • invalidate_will() (~695): PROCEDURE 1 (the "good" one). Sets label at ~704.
    • show_transaction_real() (~656) uses show_on_top(d, modal_to_window=False).
    • on-close/postpone expired handling at ~539, ~576, ~614.
  • bal/gui/qt/lists.py
    • check() (~545): the CHECK button. invalidate_will() (~571). Menu actions "Check"/"Invalidate" at ~467/468. Wizard button "Build Your Will" at ~473.
  • bal/gui/qt/plugin.py: settings dialog; "No will-executor TX" checkbox + reset.
  • bal/gui/qt/window_utils.py: show_on_top (~100), bring_to_front (~52), show_modal (~86).
  • bal/gui/qt/common.py: add_widget helper (~98); imports QTimer, Qt, etc.

7. HOW TO RESUME IN A NEW CHAT (any model)

Tell the new assistant:

"Read /home/user/webapp/HANDOFF.md, then CHANGELOG.md and .agent_memory_tasks.md. Follow rules R1R4 and zip-first. The next task is the 'unify invalidate procedure' task in HANDOFF.md section 5 — present the PLAN and wait for my OK before coding."

Everything needed (rules, state, pending task, build/test commands, file map) is in this file. The real work is safe in Git (PR #11, release v0.3.9) and in CHANGELOG.md.