Repository navigation
Conversation
jserv
force-pushed
the
desktop
branch
2 times, most recently
from
October 18, 2026 14:19
8ad4c22 to
a158ef6
Compare
This comment has been minimized.
This comment has been minimized.
1 similar comment
Contributor
Author
|
Rebase latest |
jserv
force-pushed
the
desktop
branch
5 times, most recently
from
October 24, 2026 00:12
5937688 to
eb25365
Compare
jserv
force-pushed
the
desktop
branch
5 times, most recently
from
October 24, 2026 10:01
539c6de to
543f260
Compare
This was referenced Oct 24, 2026
Without its arguments the binary falls back to web, which serves Setup until it is killed. A mutant that drops them, such as parse_agent_args returning nothing, then hangs whichever test ran the binary without a bound, and cargo mutants scores the caught mutant as a timeout, which fails the job. Only the refusal tests had a bound. Every run now goes through the bounded runner, which every caller already expected to exit, and so does the one test that started the binary itself.
A loopback bind keeps the network out but not a web page: any origin can repoint its own name at 127.0.0.1 and then read the server as same origin, including the reports behind it. The Host header is the one thing that still carries the foreign name. CODETRIAL_ALLOWED_HOSTS, or --allowed-hosts, names the Host values the server answers; anything else gets 421 before any route runs. It is empty by default, and the check is not installed at all then, because a reverse proxy forwarding the public name is an ordinary deployment. A list that names no host is refused at startup instead of read as empty, so it cannot switch the check off unnoticed.
A program that starts the server for someone, rather than an operator in a shell, chooses neither the working directory nor the folder the executable sits in, and both decide where the CLI looks for its config and writes its database, cold-start config and error log. An installed app's executable folder is read-only or inside a bundle. --data-dir DIR puts all of that in DIR and searches nowhere else. Such a launch also reads no configuration from the environment, so a shell that exported CODETRIAL_DB_PATH or NODE_ENV for a CLI deployment cannot move the install's reports depending on how it was started, and with no SESSION_SECRET in its config it makes and keeps its own key in DIR instead of signing cookies with the published default. A first launch into a folder that does not exist yet creates it for the error log, so the reason it failed is where its owner will look. One thing is taken from the environment: a first launch with the three LiveKit keys exported writes them, and the Google keys, into DIR's config as Setup would, and skips Setup. Without the flag nothing changes. That file and the key are written by the helper Setup now shares, owner-only and never over an existing name, which also removes a file a failed write cut short. The CLI tests start the server with an empty environment instead of a list of keys that had already fallen behind.
A program that starts the server as its child keeps it on a fixed port, and when that program crashes the server outlives it and holds the port, so the next launch cannot bind it. Parent-death signals and job objects differ on every platform; a pipe is closed by the kernel when its writer dies, however it dies, everywhere. With CODETRIAL_EXIT_WITH_STDIN set, the server exits once its standard input reaches end of file. It is read from the process environment even under --data-dir, because it describes the pipe the launcher handed over rather than configuring the server, and it has to hold while Setup serves. The exit does not print with eprintln, which would panic on the dead parent's stderr and end the watching thread instead.
Running CodeTrial means starting a server from a terminal and opening its address in a browser. desktop/ is a Tauri 2 app for macOS, Windows and Linux that starts the codetrial server as its child, in its own data folder on a fixed loopback port that only it may name, and shows the page in its own window. The server stays a separate program, so the CLI's startup is unchanged and the child is the binary its tests cover. The page gets no native access: only the server's origin and the app's starting page load in the window, the camera and microphone go to the server's page alone, and a close asks the page's leave-page guard first. Where WebKitGTK is built without WebRTC, which the interview's call needs, the interview opens in the default browser instead. The starting page takes the lobby's palette, accent and radii, copied since it loads before any server can serve them. The crate builds into target/desktop, and the gate builds and tests it where WebKitGTK is installed. CI's Rust lane installs it on every run, since the crate's tests read the server's side of the launch from src/main.rs and a gate that skips there fails the run. CI's dependency audit reads the crate's lockfile beside the root's.
The desktop app has to reach people as installers, and the gate's unit tests never start a real server inside a real window. The desktop workflow builds the server and the app on Linux, macOS and Windows, packages them, and runs scripts/desktop-smoke.sh against each build: the server must answer, refuse a foreign Host, and exit when the app is killed outright. A desktop-vX.Y.Z tag whose version matches desktop/Cargo.toml publishes the installers as a prerelease, apart from the CLI's rolling latest release. Close #289
Setup asked for every key by hand, even from someone whose keys were already saved in a config file, a CLI install's or another machine's. An import button on the page reads a chosen file in the browser, by the rules the server reads config files with, and fills the form. Nothing is sent until the form is saved, so the keys are checked as typed ones are. The form saves one Google key, so a GOOGLE_API_KEYS list gives its first and the page says so. A file input rather than a native dialog: the page is the server's and has no native access, and it opens in a browser too, where the desktop app's web view cannot run the interview.
A machine that already runs the CLI keeps its keys in the CLI's config, and the desktop app reads only its own data folder, so its first launch asked for them again. The app now passes the CLI config in the folder it starts in, and nowhere above it, and Setup offers it: the page names the file and its LiveKit URL, and Use these keys has the server read the file and save its Setup keys, checked as typed ones are. Applied only when the page asks, since a file found where the app started could be any project's and its LiveKit project is where interviews would go, and never shown to the page, since any account on the machine can reach the port while Setup serves. A malformed line in a config is named by number, not quoted, because it can hold a secret and the message reaches a window and a log. Exported keys are still written without asking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CodeTrial now ships as a desktop app for macOS, Windows and Linux. The app is a Tauri 2 shell in
desktop/that runs the samecodetrial webbinary as a child process and shows it in one window on a fixed127.0.0.1:3101, so no browser has to be opened by hand. Running the server as a child rather than linking it in keepssrc/main.rsclose to unchanged: the server gains three launcher options instead (--allowed-hosts,--data-dir,CODETRIAL_EXIT_WITH_STDIN), each an ordinary CLI option a non-desktop install can use too. The LiveKit and Google keys are asked for once, and come from whichever of three places has them. ExportedLIVEKIT_URL,LIVEKIT_API_KEYandLIVEKIT_API_SECRET(and optionallyGOOGLE_API_KEYorGOOGLE_API_KEYS) are written to the app's own config, checked as Setup checks them, and Setup is skipped. Otherwise the Setup page asks, and offers two shortcuts. When the app was started near a CLI config (config/codetrial.env.localorcodetrial.env.localin the start folder or above, so a run from a checkout'sdesktop/finds itsconfig/), the server's new--import-confignames that file on the page with the scheme and host of its LiveKit URL, and "Use these keys" has the server read it and save its Setup keys, checked as typed ones are. The file is never used unasked, because one found near the start folder could be any project's, and no key, no other part of its URL and no line it could not parse reaches the page, because any account on the machine can reach the port while Setup serves. And the "Import from a config file" button fills the form from a file the user picks, read in the page and checked on save like typed keys. Every later launch reads the saved config.docs/desktop.mdhas the design, the file locations and the qualification list.The page gets no native access at all; navigation, camera and microphone permission, downloads and the close check are all decided from outside the page. Where WebKitGTK is built without WebRTC (Ubuntu 24.04, Debian 12 and Arch as packaged), the app opens the interview in the default browser and keeps the server running. The app's own starting page uses the visual system the interview pages now share: the lobby's palette on a light system and the warm dark one otherwise, the violet accent, and the same radii and type steps.
Rebased onto main at 65cee24. Verified with
scripts/test.shat the tip: 1947 Rust tests pass, and the gate fails only onskill-refs, for an untracked local skill folder on this host, and on two camera-preview browser tests from #448 that time out on this host on main as well and pass in CI. The desktop lane skipped here for want of WebKitGTK; in a Debian 12 container with WebKitGTK 4.1, desktop fmt, clippy with-D warningsand the desktop unit tests pass, andscripts/desktop-smoke.shpasses against a release build under xvfb (Setup answers, a foreignHostis refused, and SIGKILL of the app takes the server with it). Every commit builds and passes clippy on its own. The desktop gate runs in the rust lane on every run, since under CI a skipped gate fails the run, and the CI dependency audit readsdesktop/Cargo.lockbeside the root lockfile. The screenshots come from that container at the app's 1280x860 window, light and dark from the system scheme. The Setup screenshot shows the import button; the page is the server's, so the CLI gets it too. Checked end to end in that container: the app started from a checkout'sdesktop/served Setup offering../config/codetrial.env.localwith its LiveKit host, wrote nothing, and sent no key to the page.Left out: GitHub sign-in and recording inside the window, signed installers, and automatic updates. An interview has not yet been run on real hardware in WKWebView, WebView2 or a WebRTC-enabled WebKitGTK; the checklist in
docs/desktop.mdis what a release needs before it is called more than a preview.Closes #289
Summary by cubic
CodeTrial now ships as a desktop app for macOS, Windows, and Linux. It starts the existing server in a child process and shows it in one window, so users don't need to launch a terminal or open a browser themselves.
Server and security
--data-dir(a launch's config, database, and session key all live in one folder; without aSESSION_SECRETthere the server generates its own key and reads no configuration from the environment, except the LiveKit and Google keys it writes to the config on a cold start, skipping Setup),--allowed-hosts(serves only theHostnames listed; empty answers any, so a proxy deployment is unaffected), andCODETRIAL_EXIT_WITH_STDIN(the server exits when its launcher's pipe closes, freeing its fixed port even if the app crashes).--import-config, or from the Setup page: its new import button reads a chosen file in the browser and fills the form, and a launched app offers the nearest CLI config—the page names the file and its LiveKit URL, read only when you tell it to.Build and releases
Written for commit 543f260. Summary will update on new commits.