Skip to content
374 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

preparing for cloud work1//! What the site may ask of the app: the commands behind `window.g1tDesktop`
2//! (bridge.js), and the launcher's own.
3//!
4//! Every command checks who is asking. The site's commands answer only a
5//! page of the configured server; the launcher's only the page bundled
6//! with the app. A page from anywhere else gets nothing, whatever it
7//! loads.
8
9use serde::Serialize;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r910use tauri::{AppHandle, Manager, UserAttentionType, Webview};
preparing for cloud work11use tauri_plugin_notification::NotificationExt;
12use tauri_plugin_opener::OpenerExt;
13use url::Url;
14
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r915use crate::settings::{self, Settings};
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.16use crate::{badge, config, frame, runner, updates};
preparing for cloud work17
18const SCRIPT: &str = include_str!("bridge.js");
19
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r920/// The bridge, run at the start of every page the site's web view loads:
21/// `custom` is whether g1t's title bar is above it (frame.rs).
22pub fn script(custom: bool) -> String {
23 SCRIPT
24 .replace("__FRAME__", if custom { "custom" } else { "system" })
25 .replace("__VERSION__", env!("CARGO_PKG_VERSION"))
26 .replace("__PLATFORM__", std::env::consts::OS)
27 .replace("\"__OUTSIDE__\"", &serde_json::to_string(crate::navigation::OUTSIDE).expect("a list of paths is JSON"))
preparing for cloud work28}
29
30/// What the site and the launcher learn about the app.
31#[derive(Serialize)]
32pub struct Info {
33 pub version: String,
34 pub platform: String,
35 pub server: String,
36 /// The version downloaded and waiting for a restart, if one is.
37 pub update_ready: Option<String>,
38}
39
40fn caller(webview: &Webview) -> Result<Url, String> {
41 webview.url().map_err(|error| error.to_string())
42}
43
44/// The page asking is one of the server's.
45fn from_site(app: &AppHandle, webview: &Webview) -> Result<(), String> {
46 let url = caller(webview)?;
47 if config::is_server(app, &url) { Ok(()) } else { Err("only g1t's own pages may ask that".into()) }
48}
49
Merge branch 'main' into rm-agents50/// The page asking is the server's, in the main window under the title
51/// bar: the one that draws the settings dialog and the runner menu.
52fn from_site_page(app: &AppHandle, webview: &Webview) -> Result<(), String> {
53 from_site(app, webview)?;
54 if webview.label() == frame::SITE { Ok(()) } else { Err("only g1t's page in the window may ask that".into()) }
55}
56
preparing for cloud work57/// The page asking is the launcher.
58fn from_launcher(webview: &Webview) -> Result<(), String> {
59 let url = caller(webview)?;
60 if config::is_launcher(&url) { Ok(()) } else { Err("only the launcher may ask that".into()) }
61}
62
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r963/// The page asking is the settings window's, bundled with the app.
64fn from_settings(webview: &Webview) -> Result<(), String> {
65 from_launcher(webview)?;
66 if webview.label() == settings::SETTINGS { Ok(()) } else { Err("only the settings window may ask that".into()) }
67}
68
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.69/// The Runner window, a page bundled with the app (launcher/runner.html).
70fn from_runner(webview: &Webview) -> Result<(), String> {
71 from_launcher(webview)?;
72 if webview.label() == settings::RUNNER { Ok(()) } else { Err("only the Runner window may ask that".into()) }
73}
74
75/// The settings window, or the Runner window, which keeps the runner's settings.
76fn from_settings_or_runner(webview: &Webview) -> Result<(), String> {
77 from_settings(webview).or_else(|_| from_runner(webview))
78}
79
preparing for cloud work80fn from_either(app: &AppHandle, webview: &Webview) -> Result<(), String> {
81 from_site(app, webview).or_else(|_| from_launcher(webview))
82}
83
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r984/// The page asking is the one under the title bar, whichever it is: the
85/// site's, the launcher, or GitHub's while signing in.
86fn from_page(app: &AppHandle, webview: &Webview) -> Result<(), String> {
87 let url = caller(webview)?;
88 let shown = config::is_server(app, &url) || config::is_launcher(&url) || crate::navigation::signing_in(&url);
89 if webview.label() == frame::SITE && shown { Ok(()) } else { Err("only the page in the window may ask that".into()) }
90}
91
92/// The page asking is the title bar's, bundled with the app.
93fn from_bar(webview: &Webview) -> Result<(), String> {
94 from_launcher(webview)?;
95 if webview.label() == frame::BAR { Ok(()) } else { Err("only the title bar may ask that".into()) }
96}
97
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.98/// The page asking has window buttons: the title bar, or the settings or Runner window's own.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r999fn from_frame(webview: &Webview) -> Result<(), String> {
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.100 from_bar(webview).or_else(|_| from_settings_or_runner(webview))
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9101}
102
preparing for cloud work103#[tauri::command]
104pub fn desktop_info(app: AppHandle, webview: Webview) -> Result<Info, String> {
105 from_either(&app, &webview)?;
106 Ok(Info {
107 version: env!("CARGO_PKG_VERSION").into(),
108 platform: std::env::consts::OS.into(),
109 server: config::server(&app).to_string(),
110 update_ready: updates::ready(&app),
111 })
112}
113
114/// A notification from the system: the site asks for one when a message
115/// is for the person and the window is behind another.
116#[tauri::command]
117pub fn notify(app: AppHandle, webview: Webview, title: String, body: String) -> Result<(), String> {
118 from_site(&app, &webview)?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9119 let settings = config::settings(&app);
120 if let Some(window) = crate::frame::window(&app).filter(|window| settings.flash && !window.is_focused().unwrap_or(false)) {
121 let _ = window.request_user_attention(Some(UserAttentionType::Informational));
122 }
123 if !settings.notifications {
124 return Ok(());
125 }
preparing for cloud work126 app.notification().builder().title(title).body(body).show().map_err(|error| error.to_string())
127}
128
129/// What needs the person, on the icon and the tray; 0 clears it.
130#[tauri::command]
131pub fn set_badge(app: AppHandle, webview: Webview, count: i64) -> Result<(), String> {
132 from_site(&app, &webview)?;
133 badge::set(&app, count.max(0));
134 Ok(())
135}
136
137/// A link to somewhere else, opened in the browser the system uses.
138#[tauri::command]
139pub fn open_url(app: AppHandle, webview: Webview, url: String) -> Result<(), String> {
140 from_either(&app, &webview)?;
141 let parsed = Url::parse(&url).map_err(|error| error.to_string())?;
142 if !matches!(parsed.scheme(), "http" | "https" | "mailto") {
143 return Err(format!("{} links stay in the app", parsed.scheme()));
144 }
145 app.opener().open_url(parsed.as_str(), None::<&str>).map_err(|error| error.to_string())
146}
147
148/// Whether a server answers at all: the launcher asks before the window
149/// goes there, so a machine with no network sees a page of ours, not the
150/// web view's own error.
151#[tauri::command]
152pub async fn reachable(app: AppHandle, webview: Webview, url: Option<String>) -> Result<bool, String> {
153 from_launcher(&webview)?;
154 let target = match url {
155 Some(text) => config::parse(&text).ok_or("That is not a web address.")?,
156 None => config::server(&app),
157 };
158 let client = reqwest::Client::builder()
159 .timeout(std::time::Duration::from_secs(6))
160 .build()
161 .map_err(|error| error.to_string())?;
162 Ok(client.head(target).send().await.is_ok())
163}
164
165/// The launcher chose a server: it is kept, and the window goes there next.
166#[tauri::command]
167pub fn set_server(app: AppHandle, webview: Webview, url: String) -> Result<String, String> {
168 from_launcher(&webview)?;
169 config::set_server(&app, &url).map(|server| server.to_string())
170}
171
172/// Looks for an update now: `current`, `ready:<version>`, or an error.
Merge branch 'main' into rm-agents173/// The settings, in their window or the dialog over the site (`shown`),
174/// show the answer themselves; anyone else asking is also told with a
175/// notification.
preparing for cloud work176#[tauri::command]
Merge branch 'main' into rm-agents177pub async fn check_for_updates(app: AppHandle, webview: Webview, shown: Option<bool>) -> Result<String, String> {
preparing for cloud work178 from_either(&app, &webview)?;
Merge branch 'main' into rm-agents179 let shown = webview.label() == settings::SETTINGS || shown == Some(true);
180 updates::check(&app, !shown).await
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9181}
182
183/// A title bar button: `minimize`, `maximize` (which toggles), `close`,
184/// `drag`, or `maximized` to ask. Answers whether the window is maximized.
185#[tauri::command]
186pub fn window_control(app: AppHandle, webview: Webview, action: String) -> Result<bool, String> {
187 from_frame(&webview)?;
188 crate::frame::control(&app, &webview.window(), &action)
189}
190
191/// The title bar's menu button: the app's menu, at that place in the window.
192#[tauri::command]
193pub fn window_menu(webview: Webview, x: f64, y: f64) -> Result<(), String> {
194 from_bar(&webview)?;
195 crate::frame::menu(&webview.window(), x, y)
196}
197
198/// The page under the title bar says what it is: its title, its search,
199/// whether there is a way back, its colours (frame.rs).
200#[tauri::command]
201pub fn title_bar_page(app: AppHandle, webview: Webview, page: frame::Page) -> Result<(), String> {
202 from_page(&app, &webview)?;
203 frame::page(&app, page);
204 Ok(())
205}
206
207/// What the title bar shows now: it asks as it loads.
208#[tauri::command]
209pub fn title_bar_current(app: AppHandle, webview: Webview) -> Result<frame::Page, String> {
210 from_bar(&webview)?;
211 Ok(frame::current(&app))
212}
213
Merge branch 'main' into rm-agents214/// A title bar button that acts on the page: `back`, `forward`, `search`,
215/// `focus`, `settings`, or `runner`, with `at` where its dot is across the
216/// bar, as a share of the bar's width.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9217#[tauri::command]
Merge branch 'main' into rm-agents218pub fn title_bar_act(app: AppHandle, webview: Webview, action: String, at: Option<f64>) -> Result<(), String> {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9219 from_bar(&webview)?;
Merge branch 'main' into rm-agents220 frame::act(&app, &action, at)
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9221}
222
223/// Restarts g1t, installing a downloaded update on the way: the settings
224/// window offers it after a change that needs one.
225#[tauri::command]
226pub fn restart(app: AppHandle, webview: Webview) -> Result<(), String> {
Merge branch 'main' into rm-agents227 from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9228 if updates::ready(&app).is_some() {
229 updates::restart(&app);
230 }
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.231 runner::shutdown(&app);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9232 app.restart()
233}
234
235/// The settings window's "Restart now": installs the downloaded update and starts it.
236#[tauri::command]
237pub fn restart_to_update(app: AppHandle, webview: Webview) -> Result<(), String> {
Merge branch 'main' into rm-agents238 from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9239 updates::restart(&app);
240 Ok(())
241}
242
Merge branch 'main' into rm-agents243/// Opens the app's settings: the site's account menu asks for it. They
244/// open over the site, or in their own window when it can't draw them.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9245#[tauri::command]
246pub fn open_settings(app: AppHandle, webview: Webview) -> Result<(), String> {
247 from_either(&app, &webview)?;
248 settings::open(&app);
249 Ok(())
250}
251
Merge branch 'main' into rm-agents252/// The site's page says it draws the settings dialog and the runner menu
253/// itself, once it can (true), or no longer (false). Until it says so, each
254/// page loaded in the window has them open in windows of their own.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9255#[tauri::command]
Merge branch 'main' into rm-agents256pub fn site_ready(app: AppHandle, webview: Webview, ready: bool) -> Result<(), String> {
257 from_site_page(&app, &webview)?;
258 config::set_site_ready(&app, ready);
259 Ok(())
260}
261
262/// What the settings show.
263#[tauri::command]
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9264pub fn get_settings(app: AppHandle, webview: Webview) -> Result<settings::View, String> {
Merge branch 'main' into rm-agents265 from_settings_or_runner(&webview).or_else(|_| from_site_page(&app, &webview))?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9266 Ok(settings::view(&app))
267}
268
Merge branch 'main' into rm-agents269/// The settings changed something: everything but opening at sign-in.
270///
271/// The site's page may change all of it but one thing: it can't take the
272/// runner out of its sandbox. Jobs that run in Docker or directly on the
273/// computer can reach the person's files, so that choice is made only in
274/// the Runner window, a page bundled with the app, and never by a page
275/// from the network, whatever it loads.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9276#[tauri::command]
277pub fn save_settings(app: AppHandle, webview: Webview, settings: Settings) -> Result<settings::View, String> {
Merge branch 'main' into rm-agents278 if from_settings_or_runner(&webview).is_err() {
279 from_site_page(&app, &webview)?;
280 if !site_may_choose(config::settings(&app).runner_backend, settings.runner_backend) {
281 return Err("How jobs run outside the sandbox is chosen in the Runner window.".into());
282 }
283 }
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9284 settings::apply(&app, settings)?;
285 Ok(settings::view(&app))
286}
287
Merge branch 'main' into rm-agents288/// Whether the site's page may change how jobs run from `before` to
289/// `after`: keeping it, or putting it back in the sandbox.
290fn site_may_choose(before: runner::Backend, after: runner::Backend) -> bool {
291 after == before || after == runner::Backend::Sandboxed
292}
293
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9294/// Opening at sign-in, which the system keeps.
295#[tauri::command]
296pub fn set_open_at_login(app: AppHandle, webview: Webview, on: bool) -> Result<settings::View, String> {
Merge branch 'main' into rm-agents297 from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9298 settings::open_at_login(&app, on)?;
299 Ok(settings::view(&app))
300}
301
302/// The settings window's "Change…": the main window goes to the launcher's choice of server.
303#[tauri::command]
304pub fn change_server(app: AppHandle, webview: Webview) -> Result<(), String> {
Merge branch 'main' into rm-agents305 from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9306 if let Some(window) = app.get_webview_window(settings::SETTINGS) {
307 let _ = window.close();
308 }
309 crate::tray::launcher(&app, "change");
310 Ok(())
311}
312
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.313/// The runner's state, diagnosis and recent log: for the site's card for
314/// this computer and the settings window.
315#[tauri::command]
316pub fn runner_status(app: AppHandle, webview: Webview) -> Result<runner::View, String> {
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.317 from_site(&app, &webview).or_else(|_| from_settings_or_runner(&webview)).or_else(|_| from_bar(&webview))?;
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.318 Ok(runner::view(&app))
319}
320
321/// The site, on every page: whether someone is signed in, the workspaces
322/// they own (the only ones they can add runners to), and the one they are
323/// in. Answers the workspace a registration token is wanted for, if one is.
324#[tauri::command]
325pub fn runner_offer(app: AppHandle, webview: Webview, signed_in: bool, workspaces: Vec<runner::Offered>, current: Option<String>) -> Result<Option<String>, String> {
326 from_site(&app, &webview)?;
327 runner::offer(&app, signed_in, workspaces, current);
328 Ok(runner::wanted(&app))
329}
330
331/// The site made a registration token for the workspace the app asked
332/// for, or says why not: `allowed` is false when the person may not add
333/// runners there. The token goes to the runner and nowhere else.
334#[tauri::command]
335pub fn runner_register(app: AppHandle, webview: Webview, workspace: String, token: Option<String>, allowed: bool, error: Option<String>) -> Result<(), String> {
336 from_site(&app, &webview)?;
337 runner::register(&app, workspace, token, allowed, error);
338 Ok(())
339}
340
341/// A button for the runner: Start, Stop, or a diagnosis's fix
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.342/// (runner/diagnosis.rs), from settings or the site's card. With a kind
343/// of job (`capability`: `linux`, `windows`), Start and Stop are its alone.
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.344#[tauri::command]
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.345pub fn runner_control(app: AppHandle, webview: Webview, action: String, capability: Option<String>) -> Result<(), String> {
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.346 from_site(&app, &webview).or_else(|_| from_settings_or_runner(&webview))?;
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.347 runner::act(&app, &action, capability.as_deref())
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.348}
349
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9350#[cfg(test)]
351mod tests {
352 #[test]
353 fn the_bridge_has_nothing_left_to_fill_in() {
354 let script = super::script(true);
355 for placeholder in ["__VERSION__", "__PLATFORM__", "__OUTSIDE__", "__FRAME__"] {
356 assert!(!script.contains(placeholder), "{placeholder} is left in the bridge");
357 }
358 assert!(script.contains(r#"var FRAME = "custom";"#));
359 assert!(super::script(false).contains(r#"var FRAME = "system";"#));
360 assert!(script.contains(r#"var OUTSIDE = ["/pricing""#));
361 }
Merge branch 'main' into rm-agents362
363 #[test]
364 fn the_site_cannot_take_the_runner_out_of_its_sandbox() {
365 use crate::runner::Backend::{Docker, Host, Sandboxed};
366 assert!(super::site_may_choose(Sandboxed, Sandboxed));
367 assert!(super::site_may_choose(Host, Host));
368 assert!(super::site_may_choose(Docker, Sandboxed));
369 assert!(super::site_may_choose(Host, Sandboxed));
370 assert!(!super::site_may_choose(Sandboxed, Host));
371 assert!(!super::site_may_choose(Sandboxed, Docker));
372 assert!(!super::site_may_choose(Docker, Host));
373 }
preparing for cloud work374}

This file's history is long; its oldest lines are credited to the oldest commit read.