build-app-preview.yml computed its patch number as git rev-list --count <latest tag>..HEAD — the exact formula build-app.yml documents as broken and replaced. It measures distance from whichever tag sorts highest, not a monotonic counter, so it resets to zero on every release and previews went backwards (0.4.62 -> 0.4.0) the moment a release landed. Ported the same "highest patch used + 1" computation build-app.yml already uses for real releases.
The installed preview also reported a bare, production-looking version, since the bundle version fields (tauri.conf.json / Cargo.toml / package.json) strip the -preview.<sha> suffix before being patched (the Windows MSI's ProductVersion has no room for one — not something verifiable without an actual Windows build, so that mechanism was left alone). Instead, preview builds now bake the suffix into the binary via a TRIPLE_C_BUILD_SUFFIX build-time env var, and get_app_version() appends it when present. A production build sets nothing, so this is a no-op there.
Added prerelease to GitHubRelease and filter on it in check_for_updates — currently a no-op against real data (nothing mirrored to GitHub is ever prerelease: true), but the updater is no longer structurally incapable of enforcing a channel split if one is ever made explicit.
Deleted sync-release.yml: workflow_dispatch-only, reading gitea.event.release.* fields a manual dispatch never populates, so it could never have actually run. build-app.yml's inline mirror already does the same job.
Refactored check_for_updates's filtering into a pure, testable pick_update helper (this file had no tests before) and added tests for it and the new get_app_version suffix handling.
cargo check / cargo clippy — clean, no new warnings
cargo test — 501 passed (7 new)
npx tsc --noEmit / npm run test — clean, 611 passed (unaffected by this change)
YAML syntax validated for the edited workflow
This PR itself is the real-world test of the preview version computation and the Windows/macOS/Linux builds, since build-app-preview.yml is the PR check
## Summary
- `build-app-preview.yml` computed its patch number as `git rev-list --count <latest tag>..HEAD` — the exact formula `build-app.yml` documents as broken and replaced. It measures distance from whichever tag sorts highest, not a monotonic counter, so it resets to zero on every release and previews went **backwards** (0.4.62 -> 0.4.0) the moment a release landed. Ported the same "highest patch used + 1" computation `build-app.yml` already uses for real releases.
- The installed preview also reported a bare, production-looking version, since the bundle version fields (tauri.conf.json / Cargo.toml / package.json) strip the `-preview.<sha>` suffix before being patched (the Windows MSI's `ProductVersion` has no room for one — not something verifiable without an actual Windows build, so that mechanism was left alone). Instead, preview builds now bake the suffix into the binary via a `TRIPLE_C_BUILD_SUFFIX` build-time env var, and `get_app_version()` appends it when present. A production build sets nothing, so this is a no-op there.
- Added `prerelease` to `GitHubRelease` and filter on it in `check_for_updates` — currently a no-op against real data (nothing mirrored to GitHub is ever `prerelease: true`), but the updater is no longer structurally incapable of enforcing a channel split if one is ever made explicit.
- Deleted `sync-release.yml`: `workflow_dispatch`-only, reading `gitea.event.release.*` fields a manual dispatch never populates, so it could never have actually run. `build-app.yml`'s inline mirror already does the same job.
- Refactored `check_for_updates`'s filtering into a pure, testable `pick_update` helper (this file had no tests before) and added tests for it and the new `get_app_version` suffix handling.
Closes #32
## Test plan
- [x] `cargo check` / `cargo clippy` — clean, no new warnings
- [x] `cargo test` — 501 passed (7 new)
- [x] `npx tsc --noEmit` / `npm run test` — clean, 611 passed (unaffected by this change)
- [x] YAML syntax validated for the edited workflow
- [ ] This PR itself is the real-world test of the preview version computation and the Windows/macOS/Linux builds, since `build-app-preview.yml` is the PR check
build-app-preview.yml computed its patch number as
`git rev-list --count <latest tag>..HEAD` — the exact formula build-app.yml
itself documents as broken and replaced (#26): a distance from whichever
tag sorts highest, not a counter, so it resets to zero on every release and
previews went backwards (0.4.62 -> 0.4.0) the moment one landed. Ported the
same "one past the highest patch already used" computation build-app.yml
uses for real releases, reading the same tags (including -mac/-win
suffixes), so a preview built right before a release now computes the
exact number that release is about to take — semver already orders
`0.4.12-preview.<sha> < 0.4.12`, so a preview user is offered the release
the moment it ships instead of being silently pinned forever.
The installed preview's reported version was also indistinguishable from
production: the bundle's own version field strips the `-preview.<sha>`
suffix before touching tauri.conf.json/Cargo.toml/package.json, since the
Windows MSI's ProductVersion has no room for one. Rather than risk that
(unverifiable without an actual Windows build), preview builds now bake
the suffix into the binary separately via a TRIPLE_C_BUILD_SUFFIX
build-time env var, and get_app_version() appends it when present — a
production build sets nothing, so this is a no-op there.
Also: added `prerelease` to `GitHubRelease` and filter on it in
check_for_updates (currently a no-op against real data — nothing mirrored
to GitHub is ever prerelease:true — but the updater is no longer
structurally incapable of enforcing a channel split if one is ever made
explicit). And deleted sync-release.yml: workflow_dispatch-only, reading
gitea.event.release.* fields a manual dispatch never populates, so it
could never have actually run; build-app.yml's inline mirror already does
the same job.
Refactored check_for_updates' filtering into a pure, testable pick_update
helper (this file had no tests before), and added tests for it and the
new get_app_version suffix handling.
Fixes#32.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
An Opus review of the previous commit found its headline claim didn't
hold: a preview and the release it precedes compute to the identical
numeric version by construction, but check_for_updates compared with a
strict `>` against the bare CARGO_PKG_VERSION (never the suffixed display
string), so `(0,4,13) > (0,4,13)` is false and the release was never
offered. Plain semver ordering doesn't make a `-preview.<sha>` suffix sort
below the same numeric release on its own here, since the comparison
never sees the suffix at all.
pick_update now takes is_preview_build, derived from whether
TRIPLE_C_BUILD_SUFFIX was baked in, and relaxes that one comparison to
`>=` — so "a release exists at my own number" reads as an update. A
production build still requires strictly newer.
Also: ported build-app.yml's `git tag --points-at HEAD` guard into the
preview version computation. Without it, workflow_dispatch (which this
workflow allows on main, not just PR builds) run on a commit a release
was already cut from would compute one past that release — reintroducing
"preview outranks production" through the manual-dispatch door. And
corrected two comments that claimed the prerelease filter was currently a
no-op: backfill-releases.yml mirrors every Gitea release to GitHub
unfiltered, prerelease flag included, so it's real defence-in-depth
against a dispatched backfill leaking a preview release, not a no-op.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
Final review pass gave this a clean bill of health overall but named
three small things:
- get_app_version() and check_for_updates() each read
option_env!("TRIPLE_C_BUILD_SUFFIX") independently with slightly
different idioms — if one were ever edited alone, the About panel and
the update check could silently disagree about whether this is a
preview build. Extracted preview_build_suffix() as the single place
that reads and classifies it.
- pick_update's doc comment described the unparseable-tag case as a
`-preview.<sha>` suffix; the actual tag build-app-preview.yml creates is
`preview-<sha>` (no version, no dot) — already correct in the
neighboring GitHubRelease::prerelease comment, just not here.
- That same prerelease comment claimed defence against a preview release
leaking through backfill-releases.yml, but a preview's tag already fails
semver parsing on its own — this field's actual job is the case parsing
can't catch: a normally-tagged release someone flags prerelease on
Gitea (a hotfix candidate, an RC) that a backfill would otherwise mirror
as-is.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
jknapp
merged commit 9b55a12b32 into main2026-08-27 17:41:38 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
build-app-preview.ymlcomputed its patch number asgit rev-list --count <latest tag>..HEAD— the exact formulabuild-app.ymldocuments as broken and replaced. It measures distance from whichever tag sorts highest, not a monotonic counter, so it resets to zero on every release and previews went backwards (0.4.62 -> 0.4.0) the moment a release landed. Ported the same "highest patch used + 1" computationbuild-app.ymlalready uses for real releases.-preview.<sha>suffix before being patched (the Windows MSI'sProductVersionhas no room for one — not something verifiable without an actual Windows build, so that mechanism was left alone). Instead, preview builds now bake the suffix into the binary via aTRIPLE_C_BUILD_SUFFIXbuild-time env var, andget_app_version()appends it when present. A production build sets nothing, so this is a no-op there.prereleasetoGitHubReleaseand filter on it incheck_for_updates— currently a no-op against real data (nothing mirrored to GitHub is everprerelease: true), but the updater is no longer structurally incapable of enforcing a channel split if one is ever made explicit.sync-release.yml:workflow_dispatch-only, readinggitea.event.release.*fields a manual dispatch never populates, so it could never have actually run.build-app.yml's inline mirror already does the same job.check_for_updates's filtering into a pure, testablepick_updatehelper (this file had no tests before) and added tests for it and the newget_app_versionsuffix handling.Closes #32
Test plan
cargo check/cargo clippy— clean, no new warningscargo test— 501 passed (7 new)npx tsc --noEmit/npm run test— clean, 611 passed (unaffected by this change)build-app-preview.ymlis the PR checkFinal review pass gave this a clean bill of health overall but named three small things: - get_app_version() and check_for_updates() each read option_env!("TRIPLE_C_BUILD_SUFFIX") independently with slightly different idioms — if one were ever edited alone, the About panel and the update check could silently disagree about whether this is a preview build. Extracted preview_build_suffix() as the single place that reads and classifies it. - pick_update's doc comment described the unparseable-tag case as a `-preview.<sha>` suffix; the actual tag build-app-preview.yml creates is `preview-<sha>` (no version, no dot) — already correct in the neighboring GitHubRelease::prerelease comment, just not here. - That same prerelease comment claimed defence against a preview release leaking through backfill-releases.yml, but a preview's tag already fails semver parsing on its own — this field's actual job is the case parsing can't catch: a normally-tagged release someone flags prerelease on Gitea (a hotfix candidate, an RC) that a backfill would otherwise mirror as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ