"Cross-Platform" Means Three Separate Platforms: Shipping a Tauri 2 App
Texodus is a Markdown editor built with Tauri 2 and Vue 3, running from one codebase on macOS, Windows, and Linux. The cross-platform promise holds perfectly — right up to the point where the code is written. Then comes delivery, and there is no shared platform there at all: there are three independent sets of problems, each with its own way of breaking on a user's machine rather than on mine.
Here is what shipping Texodus taught me about the distance between "it builds" and "they downloaded it and it started".
Linux: it did not start for everyone
I did not find this one myself. It arrived as a bug report on GitHub: the AppImage would not launch at all, dying on graphics initialization. On my machine, and on plenty of distributions, everything worked — exactly the kind of symptom you cannot reproduce where you develop.
Could not create default EGL display: EGL_BAD_PARAMETER
The cause was not in the application code but in the packaging. linuxdeploy faithfully bundles the graphics client libraries of the machine that built the app — in my case Ubuntu 22.04 on GitHub Actions. At launch they are loaded ahead of the host's own copies, and then talk to the host's Mesa driver. As long as the versions stay close, this works. On recent Mesa — the report came from Arch with RDNA4 — WebKitGTK cannot initialize EGL through that mismatched pair, and the window simply never opens.
The reasoning is the same as behind AppImage's standard excludelist: the graphics stack has to come from the host, not from the bundle. Which means those libraries have to leave the bundle.
The fix belongs in the pipeline
Fixing this by hand once would be useless — the next release would rebuild it exactly the same way. So the GitHub Actions workflow got a step that runs after the build: unpack the AppImage, delete what should not be there, and pack it back up.
find squashfs-root \( \
-name 'libwayland-*.so*' -o \
-name 'libgbm.so*' -o \
-name 'libdrm*.so*' -o \
-name 'libEGL.so*' -o \
-name 'libGL.so*' -o \
-name 'libGLX*.so*' -o \
-name 'libGLdispatch.so*' \
\) -print -delete
Then came three details, each of which cost its own round trip:
- No FUSE on the runner. Extraction goes through
--appimage-extract, andappimagetoolitself runs with--appimage-extract-and-run. - The
appimagetooldownload was dead. Assets on the old AppImageKit releases return 404, so the step pulls from the maintainedAppImage/appimagetoolrepository instead, at a pinned version. - The asset was already uploaded.
tauri-actionpublishes the AppImage before this step runs, so the finished file has to be swapped through the API: delete the old release asset, upload the repacked one.
None of this follows from the Tauri documentation. All three showed up on live releases.
macOS: not even in CI
The Texodus build matrix has two runners: ubuntu-22.04 and windows-latest. macOS is not there at all — those builds run locally, from a separate script.
The reason is signing. A shippable DMG needs a Developer ID certificate from the local keychain and notarization with Apple, which wants an app-specific password and a team ID. On top of that, a universal binary means two Rust targets (x86_64-apple-darwin and aarch64-apple-darwin) built and merged. All of this can be moved into CI — at the price of spreading certificates and secrets across someone else's infrastructure. For a project I run alone, a local script turned out to be the honest trade.
The result is one application with two different release paths: Linux and Windows build in the cloud at the push of a button, macOS builds on my Mac.
One feature, three mechanisms
The most underrated part of cross-platform work is not packaging. It is that identical behavior has to be implemented differently.
Take opening a file by double-clicking it in the file manager. On macOS the path arrives as a RunEvent::Opened event. On Windows and Linux it arrives as a command-line argument, plus a single-instance plugin so that a second launch hands the file to the running process instead of starting a second application.
The macOS branch also has a race that does not physically exist on the others: Opened can fire before the main window has been created. Until that was handled, double-clicking a .md file while the editor was closed opened two windows — one empty, one with the file. The routing logic saw no window and read that as a signal to spawn one.
The same category includes features that simply do not exist elsewhere. In Kivarion, Touch ID unlock is macOS-only, and reporting an honest "not supported" on Windows and Linux is as much a part of cross-platform work as the feature itself.
What I took away
- Test the release artifact, not the build. The EGL problem lived neither in the code nor in a dev build — only in the packaged AppImage, on someone else's system. If you do not test that, a user will, and then they will write to you about it.
- "Works on my machine" means nothing on Linux. Distributions differ in graphics stack versions more than you expect, and it is the newest environment that breaks, not the oldest.
- A platform fix has to be a pipeline step. Anything repaired by hand after the build is lost on the next release.
- Plan for different release paths. Apple's signing requirements differ enough that a single pipeline for all three platforms stops being an obvious goal.
"Cross-platform" is true about the code. About delivery, it is three separate projects that happen to share a repository.
Texodus is open source: repository and releases.
One AI signal, one tool, one MVP idea — a practical 5-minute email.
No spam, no link dumps. Unsubscribe in one click.

