fix: make the update checker visible — persistent banner + a Help menu item (#78, #79) #81

Merged
the_og merged 2 commits from fix/78-79-update-visibility into main 2026-07-20 12:52:48 -04:00
Owner

Closes #78
Closes #79

Field report: running v1.2 against a repo with v1.3.0 published gave no prompt, and there appeared to be no way to check manually.

The checker itself is fine — I hit the releases API live and it correctly returns v1.3.0 and reports 1.2.0 as outdated. It was invisible, for two independent reasons. (A third cause was specific to that build: v1.2.0 stamped update/lastCheck before running the check, so a single failed check suppressed retries for 24h. That's #37, already fixed in v1.2.1.)

#78 — the notice was written somewhere that doesn't survive a click

self.status.setText(f"Update available: … · Help ▸ About to view it.")

21 other call sites rewrite that same label. The check runs off-thread and lands a second or two after launch — precisely when the user starts interacting — so the next selection, save, or refresh wiped it.

This is the same bug fixed for the MSIX warning in #35, which got a persistent banner. That fix was never carried across to the update notice, which has the identical lifetime problem.

Fix: NoticeBanner — a persistent, dismissible notice that carries its own action button ("Open releases page"). Built as a shared widget rather than a second bespoke banner, so the next thing needing attention doesn't reach for the status bar again.

#79 — and you couldn't go looking for it either

The only "Check for updates" affordance was a button inside the About dialog. Reaching it required knowing to open About first.

Compounding that: the About action was created without a menu role. Qt auto-assigns AboutRole to actions whose text begins with "About", which relocates it into the application menu on macOS. So the notice's own hint — "Help ▸ About to view it" — pointed at a menu that on macOS doesn't contain the item. Nothing at the call site hints this happens.

Fix: Help gets its own "Check for updates…" with an explicit ApplicationSpecificRole, and the About action now states its AboutRole rather than inheriting it invisibly. The menu-driven check is never throttled and always reports back — the user asked, so silence would read as a broken button.

Where the logic lives

core.update_notice(current, release) owns both the should-we-notify decision and the wording. CI installs only pytest + cryptographyno PySide6 — so anything in bcc.py is untestable by construction, and putting this in the GUI would have meant shipping it uncovered.

One test asserts the notice text names no menu path at all. That's the thing that went stale here, so it's worth a guard rather than a comment.

Tests

366 passed, 1 skipped (+8). Ruff clean.

Newer/current/older releases; failed checks (None) and malformed payloads ({}, empty/None/non-string version, a list) all staying silent rather than producing a notice pointing at nothing; URL fallback; v-prefix handling.

Also fixed while writing the tests: the text read "Version v1.3.0 is available. You're running 1.2.0" — tags carry a v, __version__ doesn't. Both render bare now; the machine-readable version field keeps the real tag.

Not done here

The MSIX banner still uses its own QLabel. Migrating it onto NoticeBanner is the right end state — two mechanisms is the drift that caused this — but I didn't want to bundle a refactor of working, shipped behaviour into a bug fix I can't visually verify. Worth a follow-up issue.

Verification limits

No visual confirmation — PySide6 needs libEGL.so.1 to render even offscreen and I don't have root in this sandbox. The core logic is covered by tests; the banner's appearance and the macOS menu placement need a human. The macOS menu behaviour is the part I'd most want checked, since that's a platform behaviour I'm reasoning about rather than observing.

Merge order

Touches _build_menu_bar, which #80 (theming) also touches — #80 adds a View menu just above the Help menu. Small, adjacent-lines conflict if both land; whichever goes second takes both hunks.

Closes #78 Closes #79 Field report: running v1.2 against a repo with v1.3.0 published gave no prompt, and there appeared to be no way to check manually. The checker itself is fine — I hit the releases API live and it correctly returns `v1.3.0` and reports 1.2.0 as outdated. It was **invisible**, for two independent reasons. (A third cause was specific to that build: v1.2.0 stamped `update/lastCheck` *before* running the check, so a single failed check suppressed retries for 24h. That's #37, already fixed in v1.2.1.) ## #78 — the notice was written somewhere that doesn't survive a click ```python self.status.setText(f"Update available: … · Help ▸ About to view it.") ``` **21** other call sites rewrite that same label. The check runs off-thread and lands a second or two after launch — precisely when the user starts interacting — so the next selection, save, or refresh wiped it. This is the same bug fixed for the MSIX warning in #35, which got a persistent banner. That fix was never carried across to the update notice, which has the identical lifetime problem. **Fix:** `NoticeBanner` — a persistent, dismissible notice that carries its own action button ("Open releases page"). Built as a shared widget rather than a second bespoke banner, so the next thing needing attention doesn't reach for the status bar again. ## #79 — and you couldn't go looking for it either The only "Check for updates" affordance was a button *inside the About dialog*. Reaching it required knowing to open About first. Compounding that: the About action was created without a menu role. Qt auto-assigns `AboutRole` to actions whose text begins with "About", which **relocates it into the application menu on macOS**. So the notice's own hint — "Help ▸ About to view it" — pointed at a menu that on macOS doesn't contain the item. Nothing at the call site hints this happens. **Fix:** Help gets its own **"Check for updates…"** with an explicit `ApplicationSpecificRole`, and the About action now states its `AboutRole` rather than inheriting it invisibly. The menu-driven check is never throttled and always reports back — the user asked, so silence would read as a broken button. ## Where the logic lives `core.update_notice(current, release)` owns both the should-we-notify decision and the wording. CI installs only `pytest` + `cryptography` — **no PySide6** — so anything in `bcc.py` is untestable by construction, and putting this in the GUI would have meant shipping it uncovered. One test asserts the notice text names no menu path at all. That's the thing that went stale here, so it's worth a guard rather than a comment. ## Tests 366 passed, 1 skipped (+8). Ruff clean. Newer/current/older releases; failed checks (`None`) and malformed payloads (`{}`, empty/None/non-string version, a list) all staying silent rather than producing a notice pointing at nothing; URL fallback; `v`-prefix handling. Also fixed while writing the tests: the text read "Version **v1.3.0** is available. You're running **1.2.0**" — tags carry a `v`, `__version__` doesn't. Both render bare now; the machine-readable `version` field keeps the real tag. ## Not done here The MSIX banner still uses its own `QLabel`. Migrating it onto `NoticeBanner` is the right end state — two mechanisms is the drift that caused this — but I didn't want to bundle a refactor of working, shipped behaviour into a bug fix I can't visually verify. Worth a follow-up issue. ## Verification limits No visual confirmation — PySide6 needs `libEGL.so.1` to render even offscreen and I don't have root in this sandbox. The core logic is covered by tests; the banner's appearance and the macOS menu placement need a human. **The macOS menu behaviour is the part I'd most want checked**, since that's a platform behaviour I'm reasoning about rather than observing. ## Merge order Touches `_build_menu_bar`, which #80 (theming) also touches — #80 adds a View menu just above the Help menu. Small, adjacent-lines conflict if both land; whichever goes second takes both hunks.
the_og added 1 commit 2026-07-20 12:29:58 -04:00
fix: make the update checker visible -- persistent banner + a menu item
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 11s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 10s
CI / Catalog signature (pull_request) Successful in 6s
CI / Tests (py3.12 / windows-latest) (pull_request) Has been cancelled
3068e74e5c
Reported from the field: running v1.2 against a repo with v1.3.0 published
gave no prompt, and there appeared to be no way to check manually. The
checker itself works; it was invisible, for two reasons.

#78 -- the notice was written to the shared status label, which 21 other
call sites rewrite. The check runs off-thread and lands a second or two
after launch, right as the user starts clicking, so the next selection or
refresh wiped it. Exactly the bug fixed for the MSIX warning in #35, which
got a persistent banner; that fix was never carried to the update notice.

Adds NoticeBanner: a persistent, dismissible notice carrying its own action
button. It's a shared widget rather than a second bespoke banner, so the
next thing needing the user's attention doesn't reach for the status bar
again. (The MSIX banner still uses its own QLabel -- migrating it is a
follow-up, deliberately not bundled with a bug fix.)

#79 -- the only 'Check for updates' affordance was a button inside the
About dialog, which is not where anyone looks. Worse, the About action was
created without a menu role, and Qt auto-assigns AboutRole to actions whose
text begins with 'About', relocating it into the macOS application menu --
so the notice's own hint, 'Help > About to view it', pointed at a menu that
on macOS doesn't contain the item.

Help now has its own 'Check for updates...' item with an explicit
ApplicationSpecificRole, and the About action states its AboutRole rather
than inheriting it invisibly. The menu-driven check is never throttled and
always reports back -- the user asked, so silence would read as broken.

The decision and the wording live in core.update_notice() because the test
suite has no PySide6 (CI installs pytest + cryptography only), so anything
in bcc.py is untestable. A test asserts the notice text names no menu path,
which is what went stale here in the first place.

Closes #78
Closes #79
the_og added 1 commit 2026-07-20 12:51:57 -04:00
Merge branch 'main' into fix/78-79-update-visibility
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 10s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 10s
CI / Catalog signature (pull_request) Successful in 6s
CI / Tests (py3.12 / windows-latest) (pull_request) Has been cancelled
7517e16b15
Three conflicts, two of them semantic rather than textual:

- bcc.py QSS: this branch added the noticeBanner rules using the old
  module-level constants ({MUTED}, {ACCENT}); main had since moved the
  stylesheet onto palette slots ({p.muted}). Took main's form and
  translated the notice rules into it -- picking either side wholesale
  would have either dropped the banner styling or reintroduced globals
  that test_stylesheet_builder_has_no_hardcoded_colours now forbids.
- bcc.py methods: both sides appended to MainWindow (update-notice
  handlers vs theme handlers). Additive, kept both.
- tests/test_core.py: the usual EOF append. Kept both blocks.

_build_menu_bar auto-merged cleanly (View menu above, Help menu below);
verified both are present with their menu roles intact.

Verified: 272 test functions = 265 (main) + 7 (this branch), no
duplicates; 421 passed, ruff clean.
the_og merged commit 7ff4f6e5c0 into main 2026-07-20 12:52:48 -04:00
Sign in to join this conversation.