feat: notify when a newer Basecamp release is available - #320
feat: notify when a newer Basecamp release is available#320danisharora099 wants to merge 4 commits into
Conversation
Basecamp's package manager already tells you when a MODULE is out of date;
nothing told you when Basecamp itself was. A user on 0.2.1 with 0.2.3 out
shipped a stale bundled delivery_module, which presented as a runtime bug
rather than "you're three versions behind" — and cost real debugging time.
UpdateChecker is a fourth child of MainUIBackend, deliberately outside the
CoreModuleManager/UIPluginManager/PackageCoordinator triangle: it touches no
logos_core_* C API and no package_manager IPC. It compares the baked-in
VERSION against /releases/latest and, when a matching platform asset exists,
streams it to ~/Downloads and tells the user what to do with it. It does NOT
self-install.
The decision logic lives in the header-only UpdateInfo.h rather than in the
I/O shell, because tests/CMakeLists.txt links no Qt6::Network and does not
include app/utils — anything touching QNetworkAccessManager or BuildInfo.h is
structurally untestable. Keeping the rules pure buys 158 unit-test cases over
the parts that decide what to download.
Load-bearing details:
* The eligibility gate uses logos::semver::valid(), not compare(). compare()
sorts unparseable versions BELOW every real one, so without the gate every
dev build (stamped "pre-release-<sha7>") would be told it was out of date.
* QSysInfo::currentCpuArchitecture() returns "arm64" while the release assets
are named "-aarch64". That mismatch is the single most likely way this
ships silently doing nothing.
* The asset name comes from the network and becomes a filesystem path, so it
is validated as a bare filename — prefix/suffix matching alone lets
"...-v9.9.9/../../../../tmp/pwn-aarch64.dmg" through, and on Linux the
result would then be chmod +x'd.
* Both the initial asset URL and every redirect hop are host-allowlisted.
The size check is a corruption detector, not an integrity control: no
.sha256 is published, so TLS to an allowlisted host is the real guarantee.
* macOS: the quarantine xattr is restored, because downloading the file
ourselves would otherwise REMOVE the Gatekeeper assessment a browser
download would have triggered.
Co-Authored-By: Claude <noreply@anthropic.com>
…o#319) Three surfaces, chosen so a stale build is visible without a modal: * An "Update" badge under the sidebar version footer — where a user already looks to answer "what version am I on?". The 80px column fits one word. * A notification dot on the Settings button, reusing the shape of SidebarAppDelegate's missing-deps marker in the primary accent rather than red, since this is informational and not an error. Without it the badge is present but not discoverable. * An Updates card in Settings -> Dashboard carrying the state machine: Checking / UpToDate / Failed, and under UpdateAvailable the seven download stages. The card is the only card on an otherwise flat, read-only page. That contrast is the point: as loose text between "Dev build" and "Commits" the notice reads as one more line of build metadata and is easy to scroll past, which is the exact failure logos-co#319 exists to fix. Icons pass brightness: 1.0 — LogosIcon tints via MultiEffect colorization, which leaves a dark source silhouette essentially untouched, so the install glyph rendered in its raw asset color instead of the button's foreground. The whole section collapses on builds reporting Skipped (dev / pre-release / non-nix). Those users see nothing here, not an "unknown" row. Co-Authored-By: Claude <noreply@anthropic.com>
Two UI tests driven by a file:// stub, so they never depend on what GitHub currently calls "latest" and never open a socket. nix/integration-test.nix pins the stub rather than setting LOGOS_DISABLE_UPDATE_CHECK. That derivation is the ONLY automated runner of ui-tests.mjs, and the tests self-skip on the kill switch — disabling would have meant CI printed a green skip forever while the feature's UI coverage never actually executed. Hermeticity comes from the stub either way. smoke-test and shutdown-test keep the kill switch: they assert startup and teardown, not update behavior, and the check would otherwise be eligible on a release/** branch and hit api.github.com — succeeding on a sandbox=false host and DNS-failing on Linux CI, i.e. the same derivation behaving two ways. The stub publishes assets for architectures no host uses, so selectAssetName is genuinely exercised. With an empty assets[] the "unsupported platform" test asserted three values that were all field defaults and would have passed with the selection logic deleted. Co-Authored-By: Claude <noreply@anthropic.com>
logos-co#319 noted the README gives no guidance on how to update and no in-app path back to the releases page. There is now both. Also states the privacy shape plainly — one unauthenticated GET, no identifiers beyond a version-bearing user agent, never runs on dev builds, and LOGOS_DISABLE_UPDATE_CHECK=1 turns it off. Co-Authored-By: Claude <noreply@anthropic.com>
|
@dlipicar @Khushboo-dev-cpp gentle ping, requested 2026-08-07. If the size is the blocker (about 3k lines), I can split it into the version check plus notice (small) and the download-and-prompt (larger) so the first half can land on its own. Trial users are still finding out they are on an old Basecamp by asking in Discord. |
|
@
Sorry, for not closing the loop here @danisharora099 I want see if @dlipicar has different thoughts on this. after that I still need to review this PR and test it also to add @qnou0x has some new designs for this but which may not be prioritized for v0.3, depends on bandwidth left after completing other tasks - https://www.figma.com/design/iohHxlOhF9RmhBwjKB1SPj/Basecamp---MVP-v.1?node-id=1141-1868&t=UPYga8MrAtlHz9JW-4 |
|
Fair concern, and I agree it should not ship as-is. Proposal: the check is off until a first-run prompt asks, with a Settings toggle either way, and no call is ever made before consent. I will also structure the manifest fetch behind a single source so it swaps to Logos storage without a UI change once the catalog moves. That fits the split I offered: the first PR becomes consent prompt + toggle + version notice (small, reviewable), the download-and-prompt half follows separately and can wait for @qnou0x's designs if that is the direction. If @dlipicar agrees on the consent shape I will rework the branch accordingly rather than asking anyone to review 3k lines that are about to change. |
I was wondering maybe its fine for V0.3 without consent and so on cause we do it for packages anyways. but lets still wait for @dlipicar |
|
this is more of an enhancement than anything critical, so please feel free to just close this heh!
i raised this PR as I thought it's a great addition as a user (and we are open source:D), more than an eco dev eng - i wanted to upgrade my basecamp and it was a whole process, versus being able to click a button :D if this is planed for the future, please close the PR:D thanks @Khushboo-dev-cpp and team for the prompt responses as always - it's great to work with you guys. |
Summary
Basecamp had no way to tell you it was out of date. This adds a passive version check against the GitHub releases API and, when a newer stable release exists, an in-app notice plus an optional download of the matching platform artifact.
It implements tiers (a) notice and (b) download-and-prompt from #319. It does not self-install — no relaunch, no
execve, no writing into the app bundle. That's tier (c), and it wants the DMG signing work in #27 first.Basecamp's package manager already solves this for modules (
AppsModel::recomputeInstallStatus→ an "Update"LogosBadge). The host app was the one piece of the stack with zero staleness signal, which is how a user ended up on 0.2.1 with a stale bundleddelivery_moduleand spent real time debugging what looked like a product bug.Structure —
UpdateCheckeris a fourth child ofMainUIBackend, deliberately outside the CoreModuleManager/UIPluginManager/PackageCoordinator triangle: it touches nologos_core_*C API and nopackage_managerIPC. The decision logic lives in a header-onlyUpdateInfo.hrather than in the I/O shell, becausetests/CMakeLists.txtlinks noQt6::Networkand doesn't includeapp/utils— anything touchingQNetworkAccessManagerorBuildInfo.his structurally untestable. Keeping the rules pure buys 158 unit cases over the parts that decide what to download.Four commits, each independently buildable: backend, UI, tests, docs.
Three details that are load-bearing
semver::valid(), notcompare().compare()sorts unparseable versions below every real one, so without the gate every dev build (stampedpre-release-<sha7>) would be told it was out of date. There's a test that pins exactly this.QSysInfo::currentCpuArchitecture()returnsarm64; the release assets are named-aarch64. That mismatch is the single most likely way this ships silently doing nothing on every Mac and every ARM Linux box./releases/latestis ordered by creation date, not semver precedence. A hotfix cut from an old branch can legitimately be "latest", so the comparison requires a strict>and never offers a downgrade.Linked issues
Closes #319
Screenshots / recordings
All captured on macOS with
LOGOS_UPDATE_CURRENT_VERSION=0.2.1to force a stale version against the live 0.2.3 release.Before —
masterat 484da77. Dashboard goes straight from Version to Commits; no badge in the sidebar, no marker on Settings.After — an Updates card between the build summary and Commits, an
UPDATEbadge under the sidebar version, and a marker on the Settings button so the badge is discoverable without opening anything.Download states (click to expand)
Downloading — real transfer of the published 98.7 MB DMG, with cancel.
Done — resolved path, platform-correct instructions, and Show in Finder. The instructions lead with "Quit Basecamp" because replacing a running
.appis what produces "the application is damaged" reports.The section renders nothing at all on builds that report
Skipped(dev / pre-release / non-nix), so this is invisible during normal local development.Test plan
nix build .#appsucceedsnix build .#smoke-test -Lpassesnix build .#integration-test -Lpasses — 18/18, now including the two new update testsAlso run:
.#unit-tests10/10 (158 update cases),.#shutdown-test3 passed / 0 failed,.#qml-tests,.#sandbox-test.Verified by hand end to end: a real 98.7 MB download completing byte-exact against the published size, cancel mid-flight leaving no
.part, thecom.apple.quarantinexattr set withspctlreportingaccepted / source=Notarized Developer ID, the already-downloaded short-circuit, the kill switch, and the dev-build skip.One gap I can't close from this machine: the Linux AppImage. It can't be built from an aarch64-darwin host without a remote builder, and it's where the only real unknown lives — this is the first code in a shipped bundle that ever loads a Qt TLS backend. I verified the macOS half:
nix build .#bin-macos-appcarries all three Qt TLS plugins andlibqopensslbackend.dylibresolveslibssl.3/libcrypto.3via@loader_pathintoContents/Frameworks/. Qt ignoresSSL_CERT_FILEand scans its own hardcoded CA directory list, which covers mainstream distros, but someone with a Linux builder should confirm on a real AppImage. A TLS failure degrades to "couldn't check" — never to a false "up to date".Release notes
feat: notify when a newer Basecamp release is available, with optional in-app download
Checklist
fix/,feat/,chore/,docs/,test/, orci/CLAUDE.md/README.md/docs/updated if behaviour or build steps changed — README documents the notice and the opt-out.DS_Storefiles removed — screenshots are hosted on a separate branch, nothing binary is in this diffNotes for reviewers
flake.nix:58-66documents thatVERSIONexists only onrelease/**branches and master builds fall back topre-release-<sha7>; there's noCHANGELOG.md. Adding a VERSION file here would make every master build report itself as a release — which, given this feature, would turn the update checker on for master builds and tell every developer they're out of date.GETtoapi.github.com, no identifiers beyond aLogosBasecamp/<version>user agent, and noAuthorizationheader (Qt copies headers verbatim across redirects, so one would leak to the CDN).LOGOS_DISABLE_UPDATE_CHECK=1turns it off. It makes no request at all on dev builds..sha256is published, so TLS to an allowlisted host is the real guarantee. Both the initial asset URL and every redirect hop are host-allowlisted, and the asset filename is validated as a bare filename before it becomes a path. Publishing a signed digest from Jenkins is the natural follow-up — theVerifyingstage is already reserved for it.nix/integration-test.nixpins the check at afile://stub rather than settingLOGOS_DISABLE_UPDATE_CHECK. That derivation is the only automated runner ofui-tests.mjs, and the tests self-skip on the kill switch — disabling would have meant CI printed a green skip forever.smoke-testandshutdown-testkeep the kill switch, since they assert startup and teardown rather than update behaviour.DashboardView.qml; namespace the five unprefixeddownload*properties on the facade (they read as package-download state, whichPackageCoordinatoralso owns); split the NOTIFY so progress ticks don't re-evaluate four constant bindings; confirm thecom.apple.quarantineflag word empirically; disk writes currently happen on the GUI thread.🤖 Generated with Claude Code