From dcec232dfa40865f926146e7f242e67dd498a378 Mon Sep 17 00:00:00 2001 From: b3nw Date: Mon, 6 Jul 2026 17:46:30 +0000 Subject: [PATCH] fix: add btnClearUpdateLock markup for PR #5688 v2 frontend path Address Greptile P1 review (discussion r3530941293) on commit e8f7e28a: the v2 frontend recovery path was dead at the DOM level. static/ui.js references $('btnClearUpdateLock') but no matching element existed in static/index.html, so users hitting a lock conflict never saw a clickable affordance -- the error panel was the only visible state. Add the missing button next to btnForceUpdate in the update-banner action row: Style mirrors the neutral update-btn (NOT the red --error used by the destructive Force update) so the recovery affordance doesn't read as a warning. Hidden by default; revealed by the existing _showUpdateError() helper when res.lock_conflict is set. Regression tests in TestClearLockButton (3 tests): - test_clear_lock_button_exists - test_clear_lock_button_hidden_by_default - test_clear_lock_button_calls_applyClearUpdateLock_handler These lock in the contract that the button element must be present and correctly bound; future refactors that rename the id or strip the button will fail loud. Mirrors the existing TestIndexHtmlBanner conventions for btnForceUpdate. Verification: - ./scripts/test.sh tests/test_updates.py -k lock -> 35 passed - ./scripts/test.sh tests/test_update_banner_fixes.py -> 102 passed - ./scripts/test.sh tests/test_api_timeout.py -> 8 passed - ruff_lint --diff upstream/master -> 0 new violations --- static/index.html | 1 + tests/test_update_banner_fixes.py | 33 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/static/index.html b/static/index.html index 3e5acbd2..23bd8271 100644 --- a/static/index.html +++ b/static/index.html @@ -415,6 +415,7 @@ +
diff --git a/tests/test_update_banner_fixes.py b/tests/test_update_banner_fixes.py index 13bbe4f9..b9f7610e 100644 --- a/tests/test_update_banner_fixes.py +++ b/tests/test_update_banner_fixes.py @@ -1805,6 +1805,39 @@ class TestIndexHtmlBanner: ) +class TestClearLockButton: + """PR #5688 follow-up: clear-lock button must exist in index.html and be + hidden by default. Without this, the v2 frontend recovery path is dead -- + $('btnClearUpdateLock') returns null at runtime so the lock-only error + branch in _showUpdateError() never reveals a clickable affordance (P1). + """ + + def test_clear_lock_button_exists(self): + src = read('static/index.html') + assert 'id="btnClearUpdateLock"' in src, ( + "index.html must have #btnClearUpdateLock button (hidden by " + "default) -- PR #5688 v2 frontend path" + ) + + def test_clear_lock_button_hidden_by_default(self): + src = read('static/index.html') + m = re.search(r'id="btnClearUpdateLock"[^>]*>', src) + assert m, "#btnClearUpdateLock not found" + tag = m.group(0) + assert 'display:none' in tag, ( + "#btnClearUpdateLock must be hidden by default (display:none)" + ) + + def test_clear_lock_button_calls_applyClearUpdateLock_handler(self): + src = read('static/index.html') + m = re.search(r'id="btnClearUpdateLock"[^>]*>', src) + assert m, "#btnClearUpdateLock not found" + tag = m.group(0) + assert 'applyClearUpdateLock(this)' in tag, ( + "#btnClearUpdateLock onclick must invoke applyClearUpdateLock" + ) + + # ── Regression: sequential webui+agent update — restart coordination ────────── class TestSequentialUpdateRestartCoordination: