Skip to content

Commit 218f0aa

Browse files
committed
fix(tui): stop duplicate update notice at startup
The UPDATED/UPDATE_AVAILABLE startup paths fired both a footer toast and the persistent under-input line with the same text, so the restart hint rendered twice. Drop the toast and refresh the persistent notice line (bust its memo + invalidate the prompt) so a single line owns the hint.
1 parent ed8381b commit 218f0aa

3 files changed

Lines changed: 24 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ GitHub Releases page; `0.8.0` is the new starting line.
1515

1616
## Unreleased
1717

18+
- **Fix: duplicate update notices at startup.** When a background install finishes
19+
or a cached update is detected, the hint now renders only on the persistent
20+
under-input line instead of also flashing as a footer toast.
21+
1822
## 0.48.0 (2026-06-17)
1923

2024
- **Fix: tool outputs invisible on Anthropic-compatible proxies (GLM-5.2 via z.ai).**

src/pythinker_code/ui/shell/__init__.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,22 +2095,10 @@ def _pop_next_pending_approval_request(self) -> ApprovalRequest | None:
20952095
async def _auto_update(self) -> None:
20962096
# Background-refresh the cached latest version (throttled); never blocks startup.
20972097
await refresh_update_cache_if_due()
2098-
# Non-blocking shell notice based on the cached value.
2099-
notice = pending_update_notice()
2100-
if notice:
2101-
# Make version notices easy to see on macOS/Linux terminals too:
2102-
# put them at the front of the toast queue, keep them around long
2103-
# enough to survive startup redraws, and force a repaint if the
2104-
# prompt is already active.
2105-
toast(
2106-
notice,
2107-
topic="update",
2108-
duration=30.0,
2109-
immediate=True,
2110-
style="fg:ansibrightyellow bold",
2111-
)
2112-
if self._prompt_session is not None:
2113-
self._prompt_session.invalidate()
2098+
# The persistent under-input line renders the cached update hint; refresh
2099+
# it when the cache changes instead of duplicating the text as a toast.
2100+
if pending_update_notice():
2101+
self._refresh_update_notice_line()
21142102

21152103
async def _silent_auto_update(self) -> None:
21162104
"""Install a newer release silently in the background at startup."""
@@ -2147,10 +2135,15 @@ def _surface_installed_update_notice(self) -> None:
21472135
style="fg:ansiyellow",
21482136
)
21492137
return
2150-
self._update_toast(
2151-
self._installed_update_restart_notice(),
2152-
style="fg:ansibrightyellow bold",
2153-
)
2138+
# The persistent under-input line (_prepend_update_notice) already renders
2139+
# the restart message; a toast duplicates it on the footer's second row.
2140+
self._refresh_update_notice_line()
2141+
2142+
def _refresh_update_notice_line(self) -> None:
2143+
"""Drop the update-notice memo and repaint so the footer picks up new text."""
2144+
self._update_notice_cache = (0.0, None)
2145+
if self._prompt_session is not None:
2146+
self._prompt_session.invalidate()
21542147

21552148
def _installed_update_smoke_check_failed(self) -> bool:
21562149
status = read_update_status()

tests/ui_and_conv/test_silent_auto_update.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ def _toasts(monkeypatch):
3737

3838

3939
@pytest.mark.asyncio
40-
async def test_silent_update_success_toasts_restart(
40+
async def test_silent_update_success_refreshes_persistent_notice_not_toast(
4141
runtime: Runtime, tmp_path: Path, monkeypatch, _toasts
4242
):
4343
shell = _make_shell(runtime, tmp_path)
44+
invalidated: list[bool] = []
45+
shell._prompt_session = SimpleNamespace(invalidate=lambda: invalidated.append(True)) # type: ignore[assignment]
46+
shell._update_notice_cache = (time.monotonic(), "stale")
4447
monkeypatch.setattr(shell_module, "_should_auto_check_for_updates", lambda: True)
4548
monkeypatch.setattr(shell_module, "_mark_auto_update_check_attempt", lambda: None)
4649
monkeypatch.setattr(shell_module, "_detect_upgrade_command", lambda: ["pip"])
@@ -59,8 +62,9 @@ async def fake_job(**kw):
5962

6063
await shell._silent_auto_update()
6164

62-
assert any("Restart Pythinker to apply" in m for m, _ in _toasts)
63-
assert any("0.43.0" in m for m, _ in _toasts)
65+
assert _toasts == []
66+
assert invalidated == [True]
67+
assert shell._update_notice_cache == (0.0, None)
6468

6569

6670
@pytest.mark.asyncio

0 commit comments

Comments
 (0)