feat: About dialog + notify-only update checker (#18, #19) #25

Closed
the_og wants to merge 0 commits from feat/18-19-about-update-checker into main
Owner

Closes #18
Closes #19

#18 — About dialog

  • bcc_core.__version__ = "1.1.0" is now the single source of truth for the
    app version (matches pyproject.toml). Nothing else hard-codes a version
    string.
  • Added a real QMenuBar (Help menu) with About Better Claude Config….
  • AboutDialog shows: app icon (icons/twin-gears/rounded/icon-128.png),
    app name, version, a short description, and (macOS only) a note that the
    build isn't Apple-notarized.
  • Links (Repository, Issues, MIT License) are opened in the system browser
    via QDesktopServices.openUrl — never in-app navigation. The license link
    points at the raw LICENSE file on the repo. No secrets or file paths are
    ever shown in this dialog.

#19 — update checker (notify-only)

  • bcc_core.parse_version() / is_newer_version(): pure, numeric (never
    lexical) version comparison. Strips a leading v, drops pre-release/build
    suffixes, zero-pads differing-length tuples, degrades gracefully on
    malformed input instead of raising.
  • bcc_core.fetch_latest_release(): anonymous GET against the repo's public
    Gitea releases API (.../releases/latest) — no token embedded or needed
    since the repo is public. Fail-quiet: returns None on any network error,
    timeout, bad status, or malformed response. Only ever reads release
    metadata (tag + URL); never downloads or replaces the running binary.
  • UpdateCheckWorker (QThread, same pattern as ConnTester/SpawnTester)
    runs the check off the UI thread.
  • "Check for updates" button in the About dialog reports "You're up to
    date." or "vX.Y.Z available." with a button that opens the releases page.
  • Optional quiet startup auto-check: throttled to at most once/day via a
    QSettings timestamp, runs off-thread, silent on any failure, and can be
    turned off via a checkbox in the About dialog (update/autoCheck in
    QSettings).

Tests

92 → 112 passing (+20). New coverage: __version__ sanity, parse_version
(basic / v-prefix / pre-release suffix / malformed / stops-at-non-numeric),
is_newer_version (older / newer / equal / v-prefix / numeric-not-lexical /
differing-length / malformed candidate / malformed current), and
fetch_latest_release (success, falls back to RELEASES_URL when no
html_url, network failure, timeout, missing tag, malformed JSON) — all via
monkeypatch.setattr(urllib.request, "urlopen", ...), no live network in
tests
.

Verification

Ran ruff check ., ruff format --check ., and python -m pytest against
a fresh checkout of this branch (not just the working copy) — all green,
112 passed.

Notes / caveats

  • PySide6 wasn't installed in the sandbox (large download, would have blown
    the tool timeout), so bcc.py was only syntax-checked (ast.parse) and
    reviewed by hand, not import-tested under a display. bcc_core.py (where
    __version__, parse_version, is_newer_version, and
    fetch_latest_release all live) is fully unit-tested headlessly.
  • One unrelated fix folded into bcc_core.py: the existing
    _normalize_unicode non-breaking-space strip used a literal NBSP byte in
    source, which some layers of this delivery pipeline silently mangled to a
    bare space (turning the line into a no-op). Rewrote it as "\xa0"
    behavior-identical, just transmission-safe. Everything else in that
    function (smart quotes, zero-width chars) was unaffected.
  • This branch was developed in a shared/concurrent worktree — a tests/test_core.py
    timeout fix already landed on main from other work-in-progress (issue
    #12) partway through. This PR's diff is layered cleanly on top of that
    (verified against main's actual current blob, not a stale local
    checkout) and does not touch or revert it.
Closes #18 Closes #19 ## #18 — About dialog - `bcc_core.__version__ = "1.1.0"` is now the single source of truth for the app version (matches `pyproject.toml`). Nothing else hard-codes a version string. - Added a real `QMenuBar` (`Help` menu) with `About Better Claude Config…`. - `AboutDialog` shows: app icon (`icons/twin-gears/rounded/icon-128.png`), app name, version, a short description, and (macOS only) a note that the build isn't Apple-notarized. - Links (Repository, Issues, MIT License) are opened in the system browser via `QDesktopServices.openUrl` — never in-app navigation. The license link points at the raw `LICENSE` file on the repo. No secrets or file paths are ever shown in this dialog. ## #19 — update checker (notify-only) - `bcc_core.parse_version()` / `is_newer_version()`: pure, numeric (never lexical) version comparison. Strips a leading `v`, drops pre-release/build suffixes, zero-pads differing-length tuples, degrades gracefully on malformed input instead of raising. - `bcc_core.fetch_latest_release()`: anonymous GET against the repo's public Gitea releases API (`.../releases/latest`) — no token embedded or needed since the repo is public. Fail-quiet: returns `None` on any network error, timeout, bad status, or malformed response. Only ever reads release metadata (tag + URL); **never downloads or replaces the running binary**. - `UpdateCheckWorker` (`QThread`, same pattern as `ConnTester`/`SpawnTester`) runs the check off the UI thread. - "Check for updates" button in the About dialog reports "You're up to date." or "vX.Y.Z available." with a button that opens the releases page. - Optional quiet startup auto-check: throttled to at most once/day via a `QSettings` timestamp, runs off-thread, silent on any failure, and can be turned off via a checkbox in the About dialog (`update/autoCheck` in `QSettings`). ## Tests 92 → 112 passing (+20). New coverage: `__version__` sanity, `parse_version` (basic / v-prefix / pre-release suffix / malformed / stops-at-non-numeric), `is_newer_version` (older / newer / equal / v-prefix / numeric-not-lexical / differing-length / malformed candidate / malformed current), and `fetch_latest_release` (success, falls back to `RELEASES_URL` when no `html_url`, network failure, timeout, missing tag, malformed JSON) — all via `monkeypatch.setattr(urllib.request, "urlopen", ...)`, **no live network in tests**. ## Verification Ran `ruff check .`, `ruff format --check .`, and `python -m pytest` against a fresh checkout of this branch (not just the working copy) — all green, 112 passed. ## Notes / caveats - PySide6 wasn't installed in the sandbox (large download, would have blown the tool timeout), so `bcc.py` was only syntax-checked (`ast.parse`) and reviewed by hand, not import-tested under a display. `bcc_core.py` (where `__version__`, `parse_version`, `is_newer_version`, and `fetch_latest_release` all live) is fully unit-tested headlessly. - One unrelated fix folded into `bcc_core.py`: the existing `_normalize_unicode` non-breaking-space strip used a literal NBSP byte in source, which some layers of this delivery pipeline silently mangled to a bare space (turning the line into a no-op). Rewrote it as `"\xa0"` — behavior-identical, just transmission-safe. Everything else in that function (smart quotes, zero-width chars) was unaffected. - This branch was developed in a shared/concurrent worktree — a `tests/test_core.py` timeout fix already landed on `main` from other work-in-progress (issue #12) partway through. This PR's diff is layered cleanly on top of that (verified against `main`'s actual current blob, not a stale local checkout) and does not touch or revert it.
the_og added the P1 label 2026-07-07 20:44:38 -04:00
the_og added 6 commits 2026-07-07 20:44:39 -04:00
- Add `__version__ = "1.1.0"` as the single source of truth (matches pyproject.toml).
- Add parse_version()/is_newer_version() for numeric (never lexical) version
  comparison, handling v-prefix, pre-release suffixes, and malformed input.
- Add fetch_latest_release(): reads the public Gitea releases API
  (anonymous, no token) and returns {version, url} or None on any failure.
  Never downloads or touches a binary — metadata only.
- Add a real QMenuBar (Help -> About...).
- AboutDialog: app icon, name, version (core.__version__), and links to the
  repo/issues/license opened via QDesktopServices.openUrl (system browser,
  never in-app navigation). macOS unsigned-app note.
- "Check for updates" button + UpdateCheckWorker (QThread) runs
  core.fetch_latest_release() off the UI thread; shows "up to date" or
  "vX.Y.Z available" with a button to open the releases page. No binary
  download, ever.
- Optional quiet startup auto-check, throttled to once/day via a QSettings
  timestamp, off-thread, silent on failure, toggle lives in the About dialog.
test: cover __version__, parse_version/is_newer_version, and fetch_latest_release (#19)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 9s
CI / Tests (py3.12) (pull_request) Successful in 7s
3cd18392c9
All network calls are monkeypatched (urllib.request.urlopen) — no live
network in tests. Covers older/newer/equal comparisons, v-prefix,
differing-length tuples, malformed input on both sides, and
fetch_latest_release success/timeout/network-failure/malformed-response
paths.
Author
Owner

Supervisor review (Cowork): approve. CI run #160 green; isolated diff. __version__ is the single source of truth (with a sync-guard test), version compare is numeric not lexical and handles malformed tags, and fetch_latest_release is fail-quiet + tokenless + metadata-only (no binary download). About dialog leaks no secrets/paths; startup auto-check is throttled once/day and disableable. Release-time note: at v1.2.0 cut, bump __version__ and pyproject.toml to 1.2.0 together and update test_dunder_version_matches_pyproject. Ready for v1.2.0.

Supervisor review (Cowork): **approve.** CI run #160 green; isolated diff. `__version__` is the single source of truth (with a sync-guard test), version compare is numeric not lexical and handles malformed tags, and `fetch_latest_release` is fail-quiet + tokenless + metadata-only (no binary download). About dialog leaks no secrets/paths; startup auto-check is throttled once/day and disableable. **Release-time note:** at v1.2.0 cut, bump `__version__` and `pyproject.toml` to `1.2.0` together and update `test_dunder_version_matches_pyproject`. Ready for v1.2.0.
Author
Owner

Superseded by #26 (release: v1.2.0), which merged this change into main as part of the integrated release. Closing.

Superseded by #26 (release: v1.2.0), which merged this change into `main` as part of the integrated release. Closing.
the_og closed this pull request 2026-07-08 11:35:47 -04:00
Some checks are pending
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 9s
CI / Tests (py3.12) (pull_request) Successful in 7s

Pull request closed

Sign in to join this conversation.