Skip to content
This repository was archived by the owner on Sep 14, 2026. It is now read-only.

Host the existing front on Tauri v2 - #1

Closed
Pixnop wants to merge 9 commits into
devfrom
feat/tauri-skeleton
Closed

Pixnop wants to merge 9 commits into
devfrom
feat/tauri-skeleton

Conversation

@Pixnop

@Pixnop Pixnop commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

What this is

The Tauri v2 skeleton for RiftLauncher issue 18. The front is untouched: the same React under src/renderer, the same pure logic under src/domain, calling window.api exactly as it always has. What changed is everything behind that property.

The point of a skeleton is to measure the gap, not to hide it. So the Rust host does three jobs, and every other member of window.api is a stub that refuses in the vocabulary its own return type already has and says so once in the console under a fixed token. None of them invents a success. A checkPathExists that answered true because answering something felt more useful would have the renderer build a flow on a folder that is not there, and this PR would be showing a bug rather than a gap.

What the host does today: read config.json, write it back atomically, list the installations that config names, and start the game. The launch honours the installation's data folder, start parameters, environment variables, Mesa GL thread setting and Linux launch wrapper, and refuses any path outside the folders the config manages.

What it cannot do: install a version. Which means it cannot yet start a game a fresh user does not already have.

What moved and what stayed

Stayed, untouched. src/renderer/** (171 files) and src/domain/**. Nothing in either imported @src/, so the cut was a deletion rather than a rewrite: the renderer only ever reached the host through window.api, and src/domain was already free of Node and Electron.

Deleted. src/main/, src/preload/, src/ipc/, src/config/, src/utils/ (56 files), plus electron.vite.config.ts, electron-builder.yml, dev-app-update.yml, postcss.config.js, resources/, build/icons/, scripts/, tsconfig.node.json, the release and Windows-conformance workflows, and the electron, electron-vite, electron-builder, electron-updater and electron-log dependencies.

Moved into the domain, not deleted. Two pieces were host-shaped by accident:

  • normalizeConfig and the config defaults are now src/domain/config/normalize.ts. Every rule in that function is a decision about what a config field may hold; the host only ever supplied the three folder paths a fresh config falls back to, which are a parameter now. Both sides share it, and the Rust side does no validation of its own. Two normalizers would be one too many: the one that drifts is the one that stops agreeing with the file on disk.
  • MAX_BACKUP_TOTAL_BYTES and MAX_ARCHIVE_TOTAL_BYTES now sit in src/domain/installations/backupCapacity.ts, next to the arithmetic that reads them.

src/preload/preload.d.ts became src/api.d.ts. It is a type declaration, not Electron code, and it is still the contract the bridge is held to.

New. src-tauri/ (Cargo manifest, tauri.conf.json, one capability, icons, four commands across config.rs, launch.rs, paths.rs), vite.config.ts, src/renderer/src/host/tauriApi.ts, .github/workflows/ci.yml.

Honest about the stubs. Everything in this list answers a refusal and logs [host] [tauriApi] not-implemented-on-tauri <member> once:

Area Stubbed
utils getAppVersion (answers 0.0.0), setPreventAppClose, openOnBrowser, selectFolderDialog, onPreventedAppClose
appUpdater all six members: no self-update at all
modsManager all eight: no scanning, toggling, icon caching, modpack import or export, no profiles
pathsManager all nineteen: no download, extraction, compression, installer, move, delete, permissions or icon copy
gameManager lookForAGameVersion
netManager all three: nothing reaches the network
backgroundsManager both
accountManager both: no login, so no session is written into clientsettings.json

Two members are answered on the front rather than stubbed, and are marked as such in the file. getOs reads the user agent, because the renderer only asks it to pick a download and to show Linux-only settings, and a stub answering linux on a Windows machine would be a wrong answer rather than a missing one. logMessage writes to the console, which is a real destination, just not a file.

Two things in the host worth a reviewer's eye.

launch_game restates the launch plan from src/domain/versions/launch.ts in Rust instead of calling it. That is not laziness about reuse: picking the executable means listing the version folder, which the front cannot do, and adding a directory-listing command to let it would hand the renderer a capability the skeleton has no other use for. The three details a player's game actually depends on (start parameters as one argument, the executable leading the argument list under mono, the Mesa variable on the native Linux launcher only) each have a Rust test pinning them against the TypeScript. If the two ever need to be one thing, the fix is a list_version_folder command, not a copy that drifts.

saveConfig has no unauthorized-path refusal. The Electron host compared the incoming config against the previous one before accepting a folder outside the managed roots; that comparison is not ported, so the reason exists in the type and never fires. Worth closing before anyone runs this on real data.

list_installations has no caller yet. getConfig carries the installations already, so the command is there for the slice that stops reading the config wholesale, exposed as a named export on tauriApi rather than on window.api.

Testing

Front, both vitest projects (node, which runs tests/domain and the top-level suites, and renderer-dom), on this machine:

Test Files  135 passed (135)
     Tests  2380 passed (2380)

That is a green run, and three of five full runs on this machine were green. The other two each
failed two renderer-dom tests, a different pair each time, and both runs were started while a
cargo build was still competing for the machine. Every one of those tests passes when its file is
run on its own. The failures are testing-library queries giving up after their one second default
while the CPU is busy, not assertions disagreeing, and none of the four files involved is touched
by this branch: the renderer-dom half of vitest.config.ts is byte for byte the one on dev. So
this is the front's existing load sensitivity showing up on a loaded laptop rather than something
the port introduced. Worth a fix upstream, where the front lives, not here.

npm run typecheck, npm run lint:ci (0 errors, 14 pre-existing exhaustive-deps warnings), npm run format:check and npm run build all pass.

Rust: cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test (21 tests: the launch plan, the environment allow list, the wrapper resolution, the path containment rules, and the managed roots a config names).

87 test files were deleted, every one of them a test of a module that no longer exists: tests/ipc/** (52), tests/main/** (6), tests/utils/** (4, all electron-updater and electron-log), tests/config/** (3, electron-builder configuration), tests/e2e/** (3, the Windows installer), the zip and child-process fixtures (15), plus security-boundaries.test.ts, ipc-validation.test.ts, rendererErrorLog.test.ts and setup-node.ts. Nothing under tests/domain, tests/renderer, tests/renderer-dom or tests/i18n was deleted or skipped, and tests/renderer-dom/tauriApi.test.tsx is new (10 tests on the bridge).

The vitest coverage thresholds are dropped rather than carried over. They were measured against a tree with the Electron host in it, so every one of those numbers was about code that is gone. New floors get measured once the host stops being a skeleton.

npm run tauri -- build produces:

Bundle Bytes
src-tauri/target/release/bundle/appimage/RiftLauncher_1.7.0-beta.10_amd64.AppImage 100,710,904
src-tauri/target/release/bundle/deb/RiftLauncher_1.7.0-beta.10_amd64.deb 3,667,338
src-tauri/target/release/bundle/rpm/RiftLauncher-1.7.0-beta.10-1.x86_64.rpm 3,668,157

The deb and the rpm move by a few bytes between builds, so treat those two numbers as approximate rather than a checksum. The deb and the rpm are 3.7 MB because they declare WebKitGTK as a dependency and let the system provide it. The AppImage carries it, which is what AppImages do, and it is 96 MB here rather than the usual 80-ish because of the NO_STRIP below.

The first bundle run failed, and it took the deb and rpm down with it, since the AppImage is built first. linuxdeploy carries its own old binutils, and that strip cannot read the .relr.dyn section this host's linker puts in the system libraries it copies into the AppDir; it fails the whole AppImage rather than skipping the library. NO_STRIP=true skips that pass, and the run above is with it. The CI bundle job sets the same variable, with the reason written next to it.

Not tested: the app actually opening. There is no virtual display on the machine this was built on, and opening a window on a real desktop was not an option, so the binary has never been run. Everything above is static: it compiles, it bundles, the tests pass. Which means the one thing nobody has confirmed is whether the capability in src-tauri/capabilities/default.json grants enough for the webview to come up and carry an invoke. It lists core:event, core:window and core:webview and leaves out the tray, the menu, image and resource handles and the path API, on the reasoning that application commands are not gated by the access control list and nothing on the front asks for the rest. First person to run it should say whether that holds; widening it is one line.

Windows bundling is deliberately not in this PR. It needs its own installer decisions, and proving the host bundles at all is the thing worth doing first.

One follow-up commit sits on top of the ones above. src-tauri/gen/schemas was committed, and
tauri-build rewrites all four of those files, minified, on every run. So any local cargo build
left the tree dirty and turned format:check red, which is a confusing way to learn you built the
host before running the checks. They are build output, recreated from tauri.conf.json and the
capability, verified by deleting the folder and building again, so the folder is ignored now
rather than tracked. CI never read the committed copies either: it generates them during the
cargo steps, which run after format:check.

CI is green on both legs and on the bundle job. Getting there took one fix worth naming, because
it is exactly the class of thing this repository exists to find. Four launch plan tests wrote their
expectation as a forward slash string while build_plan reaches the game file through
Path::join. Linux could not tell the difference, so they passed here and on the Ubuntu leg, and
the Windows leg compared /v\Vintagestory.exe against /v/Vintagestory.exe and failed all four.
The assertions join the same way the code does now. No production code moved: it was already
right on both platforms, and only the tests were Unix shaped.

The sonarcloud job is red and stays red until someone with repository settings adds a
SONAR_TOKEN and creates the project. It is continue-on-error: true for exactly this reason, so
it does not gate the merge, but it will keep showing a red tick on the checks list until the new
repository is wired up the way the front's is.

The front is the part this repository is evaluating, so it stays exactly as
it is; everything that existed only to host it under Electron goes.

Gone: src/main, src/preload, src/ipc, src/config and src/utils, with the
tests that covered them, plus electron-vite, electron-builder, the release
and Windows conformance workflows, and the electron, electron-updater and
electron-log dependencies. Nothing in src/renderer or src/domain imported
any of it: the renderer only ever reached the host through window.api, and
src/domain was already free of Node and Electron, which is what made the
cut a deletion rather than a rewrite.

Two pieces were host-shaped by accident rather than by nature and moved
into the domain instead of being deleted:

- normalizeConfig, now src/domain/config/normalize.ts. Every rule in it is
  a decision about what a config field may hold, and the host only ever
  supplied the three folder paths a fresh config falls back to. Those are
  a parameter now, so the function is pure and both sides can share it.
- the two archive size ceilings a backup is measured against, now in
  src/domain/installations/backupCapacity.ts next to the arithmetic that
  reads them.

The renderer builds through vite.config.ts with the same two aliases it had
and the Tailwind v4 plugin, rooted at src/renderer so index.html keeps
loading /src/main.tsx as before. The document's CSP meta tag moves to the
Tauri configuration, which serves the policy as a header and rewrites it to
admit its own IPC; two policies would intersect and cut that off.

Vitest keeps both projects. Coverage thresholds are dropped rather than
carried over: they were measured on a tree with the Electron host in it.
src-tauri is a Rust binary with four commands in three jobs: read_config and
write_config for the config file, list_installations for what it lists, and
launch_game for starting a version. The window is 1280x720 with a 1024x600
floor, the identifier is dev.stratumvs.riftlauncher.tauri, the bundle targets
are AppImage, deb and rpm, and the icons come from the set the launcher
already ships.

The config is read and written here and validated there. Rust does file IO
and nothing else with the document: it hands over the raw JSON plus the three
fallback folders, and normalizeConfig, the same function the Electron host
called, decides what the fields may hold. A second normalizer in Rust would
be the one that drifts, and the one that drifts is the one that stops
agreeing with the file on disk. Writes go through a temporary file in the
destination's own directory, flushed before the rename, so a machine that
loses power leaves the previous config rather than an empty one.

launch_game is the exception, and it is an honest one. The renderer names a
version and an installation by id; every path, argument, environment variable
and wrapper is read out of the saved config by the host itself, and both
folders have to sit inside a root that config names before anything is
spawned, lexically and again after symbolic links are resolved. That check is
the reason the plan is restated in Rust rather than computed on the front:
picking the executable means listing the version folder, which the front
cannot do. The three details a player's game depends on, the single
start-parameters argument, the executable leading the arguments under mono,
and the Mesa variable on the native Linux launcher only, each have a test.

The capability grants the core event, window and webview surface. The
commands this host defines are application commands, which the access control
list does not gate; the tray, the menu, image and resource handles and the
path API are left out because nothing asks for them.

On the front, src/renderer/src/host/tauriApi.ts implements the whole window.api
shape and main.tsx installs it before the first render, so no page, hook or
adapter changed and the renderer-dom tests keep installing their own mock over
the same property. Three members are real calls. The rest refuse in the
vocabulary their own return type already has and say so once under a fixed
token. None of them invents a success: a checkPathExists that answered true
would have the renderer build a flow on a folder that is not there.
One job per platform runs the front and the host together: npm ci, typecheck,
lint, format, the whole vitest run, then cargo fmt, clippy with warnings
denied, the Rust tests and a build. A Rust change that breaks the front should
turn one leg red rather than two unrelated ones.

A second job bundles on Linux and uploads the AppImage and the deb. It builds
through the @tauri-apps/cli devDependency, so the bundle comes from the version
the lockfile pins. The Windows bundle needs its own installer decisions and
waits for a pass of its own.

The Sonar project key and exclusions follow the tree: no out/ or resources/ any
more, and the Rust target and icon directories stay out of the scan.
The README opened on a launcher people download. This one is not that: it is
the Tauri host behind RiftLauncher issue 18, and the first thing a reader needs
is that the downloads are somewhere else.

It also says the part that is easy to get wrong: the front is shared but not
symmetrically. A renderer or domain fix that lands here first is a fix that
gets lost the next time the two repositories are reconciled, so it belongs in
StratumServer/RiftLauncher and gets cherry-picked across.

The sections about what the launcher is, privacy, translation and support are
kept. The feature list went, since the paragraph above it already said the
same thing, and two links into a docs tree this repository does not have went
with it. docs/architecture.md keeps a note saying it still describes the
Electron host.
build/icons was electron-builder's icon set; the Tauri host generates its own
from the same source into src-tauri/icons, so nothing reads these any more.
The VS Code launch configuration started electron-vite, which is not installed;
a debug entry that runs a missing binary is worse than none, and the dev loop
is npm run tauri:dev. The extension recommendations pick up Tauri and
rust-analyzer.
Every local bundle run died the same way: linuxdeploy carries an old binutils,
and that strip cannot read the .relr.dyn section modern linkers put in the
system libraries it copies into the AppDir. It does not skip the library, it
fails the whole AppImage, and the deb and rpm never get built because the
AppImage runs first.

NO_STRIP skips that pass. The AppImage then carries unstripped copies of
libraries it was going to carry either way, which is a few megabytes against a
bundle that does not build at all.
@Pixnop
Pixnop requested a review from Zaldaryon September 13, 2026 21:51
tauri-build rewrites src-tauri/gen/schemas on every run, and it writes
them minified, while the copies in the tree were formatted by prettier.
So any local cargo build left four tracked files dirty and turned
format:check red, which is a confusing way to learn that you built the
host before running the checks.

They are build output. The build script recreates all four from
tauri.conf.json and the capability, verified by deleting the folder and
building again, so nothing needs the committed copies. CI never did
either: it generates them during the cargo steps, which run after
format:check.

Prettier 3 reads .gitignore as well as .prettierignore, so ignoring the
folder settles both halves at once.
Four of the launch plan tests built their expectation as a string with
forward slashes, while build_plan reaches the game file through
Path::join. On Linux the two happen to agree, so the tests passed here
and on the Ubuntu leg, and the Windows leg read /v\Vintagestory.exe
against /v/Vintagestory.exe and failed all four.

The rules under test are the argument shape, the mono fallback and the
wrapper, none of which is about separators, so the expectations now join
the same way the code does and the tests go back to being about the
rules. No production code changed: it was already right on both
platforms, and only the assertions were Unix shaped.
@Pixnop

Pixnop commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Closing without merging: the team decided to stay on Electron (RiftLauncher issue 18). This branch and docs/notes/tauri-spike.md stay as the reference for the spike; the repository is archived.

@Pixnop Pixnop closed this Sep 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant