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, allprerelease=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
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:
Preview versions are non-monotonic — they go backwards on every release, so a preview user is not offered newer previews either.
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:
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
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:
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.
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.
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.
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.
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.
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.ymlpublishes previews to Gitea only, taggedpreview-<sha>, markedprerelease: true. Its header says "No GitHub sync" and that is accurate.build-app.ymlpublishes production to Gitea and mirrors to GitHub inline (:736-758) with a hardcoded"prerelease": false.check_for_updatesreadshttps://api.github.com/repos/shadowdao/triple-c/releases, which today holds 14 releases, allprerelease=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.ymlcomputes its patch as:That is verbatim the formula
build-app.yml:45-60documents 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:
v0.4.10And the reset is live: after
v0.4.11was tagged,git rev-list --count v0.4.11..mainis 0, so the next preview numbers itself0.4.0. Preview versions are about to run 0.4.62 -> 0.4.0.Two consequences:
MAJOR.MINOR, and nothing keeps them apart.Defect 2 — an installed preview reports a bare production-looking version
build-app-preview.yml:200strips the marker before patching the bundle:So
get_app_version()and the About UI both report0.4.62, indistinguishable from a production build. Combined with defect 1, a preview user reports a version above every stable release andcheck_for_updates(which filters onver > 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.dProductVersion. 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
prereleaseat allmodels/update_info.rs:23-29:No
prereleasefield, andupdate_commands.rsnever 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.ymlis dead codeIt is
on: workflow_dispatchonly, yet every value it reads isgitea.event.release.*, which is empty on a manual dispatch. It cannot work as written. The actual mirroring happens inline inbuild-app.yml. Either delete it or give it areleasetrigger — as it stands it reads like the mechanism and is not.Suggested direction
Given the intent to split pre-release and production properly:
0.4.12-preview.<sha>against a coming0.4.12. Semver already orders0.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.get_app_version) so an installed preview is never mistaken for production.prereleasetoGitHubReleaseand filter on it, so the channel is enforced in the app rather than by the accident of what gets mirrored.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.