Preview versions are non-monotonic and outrank production, so a preview install can never update #32

Closed
opened 2026-08-25 18:18:19 +00:00 by jknapp · 0 comments
Owner

The channel split mostly exists already — the numbering is what's broken

Worth stating up front, because it changes the shape of the work: previews and production are already separated at the distribution layer.

  • build-app-preview.yml publishes previews to Gitea only, tagged preview-<sha>, marked prerelease: true. Its header says "No GitHub sync" and that is accurate.
  • build-app.yml publishes production to Gitea and mirrors to GitHub inline (:736-758) with a hardcoded "prerelease": false.
  • check_for_updates reads https://api.github.com/repos/shadowdao/triple-c/releases, which today holds 14 releases, all prerelease=false. No preview has ever reached it.

So the feed is clean. What is broken is the version numbers, which is what makes semver unable to express the relationship between the two channels.

Defect 1 — preview numbering is the formula the release workflow condemns

build-app-preview.yml computes its patch as:

PATCH=$(git rev-list --count "${LATEST_TAG}..HEAD")

That is verbatim the formula build-app.yml:45-60 documents as broken and replaced for releases in #26"not a counter at all: it measures how far HEAD has drifted from whichever tag sorts highest, and that resets to zero every time a tag is cut." It was fixed on the release side and left in place here.

Observed 2026-08-25:

Highest release tag v0.4.10
Commits since it 62
Preview published as 0.4.62
Production published minutes later as 0.4.11

And the reset is live: after v0.4.11 was tagged, git rev-list --count v0.4.11..main is 0, so the next preview numbers itself 0.4.0. Preview versions are about to run 0.4.62 -> 0.4.0.

Two consequences:

  1. Preview versions are non-monotonic — they go backwards on every release, so a preview user is not offered newer previews either.
  2. They will eventually collide with real release numbers. The preview line and the production line draw from the same MAJOR.MINOR, and nothing keeps them apart.

Defect 2 — an installed preview reports a bare production-looking version

build-app-preview.yml:200 strips the marker before patching the bundle:

BASE_VERSION="$(echo '...' | cut -d'-' -f1)"   # 0.4.62-preview.eead748 -> 0.4.62

So get_app_version() and the About UI both report 0.4.62, indistinguishable from a production build. Combined with defect 1, a preview user reports a version above every stable release and check_for_updates (which filters on ver > current_semver) never offers them anything. They are silently pinned.

The existing comment gives the reason and it is a real constraint — "Tauri / Cargo require a strict semver". Cargo accepts prerelease suffixes fine; the likely blocker is the Windows MSI, since WiX wants a numeric a.b.c.d ProductVersion. Any fix has to confirm what the bundlers actually accept rather than assuming the suffix can simply be kept.

Defect 3 (latent) — the updater cannot see prerelease at all

models/update_info.rs:23-29:

pub struct GitHubRelease {
    pub tag_name: String,
    pub html_url: String,
    pub body: String,
    pub assets: Vec<GitHubAsset>,
    pub published_at: String,
}

No prerelease field, and update_commands.rs never mentions one. Harmless today because nothing marks a mirrored release as prerelease — but it means the app is structurally incapable of honouring a channel, so the current separation rests entirely on previews never being mirrored. If a channel split is ever made explicit in the app (an opt-in "receive previews" setting), this is the first thing that has to change.

Defect 4 (tidiness) — sync-release.yml is dead code

It is on: workflow_dispatch only, yet every value it reads is gitea.event.release.*, which is empty on a manual dispatch. It cannot work as written. The actual mirroring happens inline in build-app.yml. Either delete it or give it a release trigger — as it stands it reads like the mechanism and is not.

Suggested direction

Given the intent to split pre-release and production properly:

  1. Number previews from the same monotonic base as releases (highest tag + 1), so a preview is 0.4.12-preview.<sha> against a coming 0.4.12. Semver already orders 0.4.12-preview.x < 0.4.12, so a preview user is offered the stable release the moment it lands — which is the behaviour wanted.
  2. Keep the suffix in the bundle version if the bundlers allow it; verify per-platform rather than assuming. If MSI cannot take it, carry the marker some other way (a build-time env var surfaced by get_app_version) so an installed preview is never mistaken for production.
  3. Add prerelease to GitHubRelease and filter on it, so the channel is enforced in the app rather than by the accident of what gets mirrored.
  4. Delete or fix sync-release.yml.

Priority

Low for the maintainer, who installs manually. It matters for anyone else running a preview, and defect 1's collision risk grows over time. Filed 2026-08-25 after PR #30 merged.

## The channel split mostly exists already — the numbering is what's broken Worth stating up front, because it changes the shape of the work: **previews and production are already separated at the distribution layer.** - `build-app-preview.yml` publishes previews to **Gitea only**, tagged `preview-<sha>`, marked `prerelease: true`. Its header says "No GitHub sync" and that is accurate. - `build-app.yml` publishes production to Gitea **and** mirrors to GitHub inline (`:736-758`) with a hardcoded `"prerelease": false`. - `check_for_updates` reads `https://api.github.com/repos/shadowdao/triple-c/releases`, which today holds 14 releases, **all** `prerelease=false`. No preview has ever reached it. So the feed is clean. What is broken is the *version numbers*, which is what makes semver unable to express the relationship between the two channels. ## Defect 1 — preview numbering is the formula the release workflow condemns `build-app-preview.yml` computes its patch as: ```sh PATCH=$(git rev-list --count "${LATEST_TAG}..HEAD") ``` That is verbatim the formula `build-app.yml:45-60` documents as broken and replaced for releases in #26 — *"not a counter at all: it measures how far HEAD has drifted from whichever tag sorts highest, and that resets to zero every time a tag is cut."* It was fixed on the release side and left in place here. Observed 2026-08-25: | | | |---|---| | Highest release tag | `v0.4.10` | | Commits since it | 62 | | Preview published as | **0.4.62** | | Production published minutes later as | **0.4.11** | And the reset is live: after `v0.4.11` was tagged, `git rev-list --count v0.4.11..main` is **0**, so the next preview numbers itself `0.4.0`. Preview versions are about to run 0.4.62 -> 0.4.0. Two consequences: 1. **Preview versions are non-monotonic** — they go backwards on every release, so a preview user is not offered newer previews either. 2. **They will eventually collide with real release numbers.** The preview line and the production line draw from the same `MAJOR.MINOR`, and nothing keeps them apart. ## Defect 2 — an installed preview reports a bare production-looking version `build-app-preview.yml:200` strips the marker before patching the bundle: ```sh BASE_VERSION="$(echo '...' | cut -d'-' -f1)" # 0.4.62-preview.eead748 -> 0.4.62 ``` So `get_app_version()` and the About UI both report `0.4.62`, indistinguishable from a production build. Combined with defect 1, a preview user reports a version above every stable release and `check_for_updates` (which filters on `ver > current_semver`) never offers them anything. They are silently pinned. **The existing comment gives the reason and it is a real constraint** — "Tauri / Cargo require a strict semver". Cargo accepts prerelease suffixes fine; the likely blocker is the Windows MSI, since WiX wants a numeric `a.b.c.d` ProductVersion. Any fix has to confirm what the bundlers actually accept rather than assuming the suffix can simply be kept. ## Defect 3 (latent) — the updater cannot see `prerelease` at all `models/update_info.rs:23-29`: ```rust pub struct GitHubRelease { pub tag_name: String, pub html_url: String, pub body: String, pub assets: Vec<GitHubAsset>, pub published_at: String, } ``` No `prerelease` field, and `update_commands.rs` never mentions one. Harmless today because nothing marks a mirrored release as prerelease — but it means the app is *structurally* incapable of honouring a channel, so the current separation rests entirely on previews never being mirrored. If a channel split is ever made explicit in the app (an opt-in "receive previews" setting), this is the first thing that has to change. ## Defect 4 (tidiness) — `sync-release.yml` is dead code It is `on: workflow_dispatch` only, yet every value it reads is `gitea.event.release.*`, which is empty on a manual dispatch. It cannot work as written. The actual mirroring happens inline in `build-app.yml`. Either delete it or give it a `release` trigger — as it stands it reads like the mechanism and is not. ## Suggested direction Given the intent to split pre-release and production properly: 1. Number previews from the **same monotonic base** as releases (highest tag + 1), so a preview is `0.4.12-preview.<sha>` against a coming `0.4.12`. Semver already orders `0.4.12-preview.x < 0.4.12`, so a preview user is offered the stable release the moment it lands — which is the behaviour wanted. 2. Keep the suffix in the bundle version **if the bundlers allow it**; verify per-platform rather than assuming. If MSI cannot take it, carry the marker some other way (a build-time env var surfaced by `get_app_version`) so an installed preview is never mistaken for production. 3. Add `prerelease` to `GitHubRelease` and filter on it, so the channel is enforced in the app rather than by the accident of what gets mirrored. 4. Delete or fix `sync-release.yml`. ## Priority Low for the maintainer, who installs manually. It matters for anyone else running a preview, and defect 1's collision risk grows over time. Filed 2026-08-25 after PR #30 merged.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#32