Step 4 of M314. `desktop/packaging/build-version.sh` was one generator feeding the desktop bundles AND the Android APK off `GITHUB_RUN_NUMBER`, so a Kotlin-only commit re-versioned the desktop and a Rust-only commit re-versioned the phone. Note 3127 §3 cites this repo as its example of that failure. It is replaced by `packaging/version.sh` — one definition of HOW to derive, three file sets, and the sets in one place. Lives at the repo root rather than under desktop/, because it serves three artifacts now and a shared thing filed under one consumer ends up owned by it. ## Two values, and the clock chosen per value (§2) desktop key 1.0.<minutes since 2020-01-01> commit time desktop display 2026.08.28.0900 commit time (#3181 shows it) android versionName commit time android versionCode <minutes since 2020> BUILD time server version 2026.08.28.0900 commit time, no ordering key Every human-readable version in the repo is now one shape. The two exceptions are not version names at all — they are bare monotonic integers a comparator reads and nobody quotes. The desktop needs a separate key because Tauri parses `latest.json` with the semver crate and `2026.08.28.0900` fails it twice (four segments, and `08` is a leading zero). `1.0.` and not `0.0.`: the minor has to clear the installed `0.2.466` line or every dev user is stranded on "up to date" permanently. Android's code comes from BUILD time while the desktop's key comes from COMMIT time, deliberately. Android hard-fails a downgrade with INSTALL_FAILED_VERSION_DOWNGRADE and leaves a channel you cannot get out of, so its key must be monotonic by construction; the desktop merely declines to offer an update, which a guard can catch. ## The bug this found in itself The shallow-clone guard `exit 1`-ed inside a function called as `$(...)` — which ends the SUBSHELL, not the script. `display` still failed, but only because `date` then choked on the empty string. `key` printed the error to stderr, emitted `1.0.-26297280`, and exited ZERO. That is precisely the failure the guard exists to prevent: a too-low version on a green lane, and too-low is the direction you cannot recover from. It resolves into a global in the parent shell now. The test is parametrized over both requests, because one path was covered and the other was broken in exactly the way the covered one was meant to rule out. ## Also `fetch-depth: 0` on every job that derives — four of them, and only ci.yml's gate had it. Depth-1 is silently wrong rather than loudly broken (§6.1). The file sets include each artifact's BUILD RECIPE (its workflow, and `packaging/`). A workflow file is not shipped, but change a Gradle flag and the bytes change while the source does not — and once step 6 skips a build whose version already exists, that serves the OLD artifact on a green run. The base images are deliberately NOT resolved at derive time: that is an external lookup, which §7's corollary forbids. `Dockerfile` is already in the server's set, so pinning `FROM` by digest in step 6 puts the base inside the set for free. `build-version.sh` is deleted, its last consumer (the pacman packager) moved over, and the one finding worth keeping out of its header — why not a `-dev.N` prerelease — is preserved in the successor. #3144 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThoughtSync desktop — Arch package
A prebuilt native pacman package, published as an asset on every ThoughtSync release. Nothing to compile, no toolchain to install.
Installing natively on Arch matters for more than tidiness: pacman pulls
webkit2gtk-4.1 itself and the app runs against your system's graphics stack,
which is what keeps the bundled-library EGL_BAD_PARAMETER black window
(issue 2021) from coming back. It also means the app is package-manager tracked
and uninstalls cleanly.
Install
Easiest — the one-command installer picks this package automatically on any pacman system:
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh
That installs the newest build from main. To follow the rolling development
channel instead, pass the flag through the pipe:
curl -fsSL https://git.fabledsword.com/bvandeusen/thoughtsync/raw/branch/dev/desktop/packaging/install.sh | sh -s -- --channel dev
Or grab the .pkg.tar.* from the
releases page
and install it directly:
sudo pacman -U thoughtsync-*-x86_64.pkg.tar.*
The compression suffix depends on what the build image provides — .zst when
zstd is installed, otherwise .xz (today's builds are .xz). pacman reads
all of them; only the filename differs.
Either way you get:
/usr/bin/thoughtsync— the app/usr/share/applications/thoughtsync.desktop— the menu entry/usr/share/icons/hicolor/*/apps/thoughtsync.png— themed icons
Launch ThoughtSync from your app menu, or run thoughtsync.
Uninstall: sudo pacman -R thoughtsync.
The package was called thoughtsync-desktop before; it declares replaces/
conflicts on that name, so an upgrade from it is a normal pacman -U and
leaves nothing behind.
How the package is built
package-prebuilt.sh runs in CI (.forgejo/workflows/desktop.yml) and wraps the
binary the Linux build already produced into a pacman package — it does not
compile anything a second time.
Packaging a Debian-compiled binary for Arch is safe here because the binary
bundles nothing: it resolves libwebkit2gtk-4.1.so.0, libgtk-3.so.0 and
libsoup-3.0.so.0 by SONAME at runtime and those are identical on both distros,
SQLite is compiled in (rusqlite "bundled"), and glibc's forward compatibility
means building on Debian's older glibc and running on Arch's newer one is the
safe direction.
CI is Debian and has no pacman, so it cannot install-test the result. The build
step instead logs the package's .PKGINFO and complete file listing, so the
package is auditable from the run log; a real pacman -U is the final proof.
Previously
This directory used to hold a source-build PKGBUILD requiring makepkg -si
with rust + nodejs + npm installed. It was removed once prebuilt packages
shipped — asking every user to install a compiler toolchain isn't distribution.