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 work | 1 | //! 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 | ||
| 9 | use serde::Serialize; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 10 | use tauri::{AppHandle, Manager, UserAttentionType, Webview}; |
| preparing for cloud work | 11 | use tauri_plugin_notification::NotificationExt; |
| 12 | use tauri_plugin_opener::OpenerExt; | |
| 13 | use url::Url; | |
| 14 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 15 | use 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. | 16 | use crate::{badge, config, frame, runner, updates}; |
| preparing for cloud work | 17 | |
| 18 | const SCRIPT: &str = include_str!("bridge.js"); | |
| 19 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 20 | /// 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). | |
| 22 | pub 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 work | 28 | } |
| 29 | ||
| 30 | /// What the site and the launcher learn about the app. | |
| 31 | #[derive(Serialize)] | |
| 32 | pub 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 | ||
| 40 | fn 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. | |
| 45 | fn 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-agents | 50 | /// 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. | |
| 52 | fn 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 work | 57 | /// The page asking is the launcher. |
| 58 | fn 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-7nm8r9 | 63 | /// The page asking is the settings window's, bundled with the app. |
| 64 | fn 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). |
| 70 | fn 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. | |
| 76 | fn from_settings_or_runner(webview: &Webview) -> Result<(), String> { | |
| 77 | from_settings(webview).or_else(|_| from_runner(webview)) | |
| 78 | } | |
| 79 | ||
| preparing for cloud work | 80 | fn 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-7nm8r9 | 84 | /// 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. | |
| 86 | fn 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. | |
| 93 | fn 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-7nm8r9 | 99 | fn 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-7nm8r9 | 101 | } |
| 102 | ||
| preparing for cloud work | 103 | #[tauri::command] |
| 104 | pub 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] | |
| 117 | pub 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-7nm8r9 | 119 | 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 work | 126 | 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] | |
| 131 | pub 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] | |
| 139 | pub 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] | |
| 152 | pub 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] | |
| 167 | pub 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-agents | 173 | /// 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 work | 176 | #[tauri::command] |
| Merge branch 'main' into rm-agents | 177 | pub async fn check_for_updates(app: AppHandle, webview: Webview, shown: Option<bool>) -> Result<String, String> { |
| preparing for cloud work | 178 | from_either(&app, &webview)?; |
| Merge branch 'main' into rm-agents | 179 | 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-7nm8r9 | 181 | } |
| 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] | |
| 186 | pub 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] | |
| 193 | pub 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] | |
| 201 | pub 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] | |
| 209 | pub 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-agents | 214 | /// 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-7nm8r9 | 217 | #[tauri::command] |
| Merge branch 'main' into rm-agents | 218 | pub 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-7nm8r9 | 219 | from_bar(&webview)?; |
| Merge branch 'main' into rm-agents | 220 | frame::act(&app, &action, at) |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 221 | } |
| 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] | |
| 226 | pub fn restart(app: AppHandle, webview: Webview) -> Result<(), String> { | |
| Merge branch 'main' into rm-agents | 227 | from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 228 | 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-7nm8r9 | 232 | app.restart() |
| 233 | } | |
| 234 | ||
| 235 | /// The settings window's "Restart now": installs the downloaded update and starts it. | |
| 236 | #[tauri::command] | |
| 237 | pub fn restart_to_update(app: AppHandle, webview: Webview) -> Result<(), String> { | |
| Merge branch 'main' into rm-agents | 238 | from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 239 | updates::restart(&app); |
| 240 | Ok(()) | |
| 241 | } | |
| 242 | ||
| Merge branch 'main' into rm-agents | 243 | /// 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-7nm8r9 | 245 | #[tauri::command] |
| 246 | pub 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-agents | 252 | /// 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-7nm8r9 | 255 | #[tauri::command] |
| Merge branch 'main' into rm-agents | 256 | pub 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-7nm8r9 | 264 | pub fn get_settings(app: AppHandle, webview: Webview) -> Result<settings::View, String> { |
| Merge branch 'main' into rm-agents | 265 | 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-7nm8r9 | 266 | Ok(settings::view(&app)) |
| 267 | } | |
| 268 | ||
| Merge branch 'main' into rm-agents | 269 | /// 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-7nm8r9 | 276 | #[tauri::command] |
| 277 | pub fn save_settings(app: AppHandle, webview: Webview, settings: Settings) -> Result<settings::View, String> { | |
| Merge branch 'main' into rm-agents | 278 | 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-7nm8r9 | 284 | settings::apply(&app, settings)?; |
| 285 | Ok(settings::view(&app)) | |
| 286 | } | |
| 287 | ||
| Merge branch 'main' into rm-agents | 288 | /// Whether the site's page may change how jobs run from `before` to |
| 289 | /// `after`: keeping it, or putting it back in the sandbox. | |
| 290 | fn 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-7nm8r9 | 294 | /// Opening at sign-in, which the system keeps. |
| 295 | #[tauri::command] | |
| 296 | pub fn set_open_at_login(app: AppHandle, webview: Webview, on: bool) -> Result<settings::View, String> { | |
| Merge branch 'main' into rm-agents | 297 | from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 298 | 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] | |
| 304 | pub fn change_server(app: AppHandle, webview: Webview) -> Result<(), String> { | |
| Merge branch 'main' into rm-agents | 305 | from_settings(&webview).or_else(|_| from_site_page(&app, &webview))?; |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 306 | 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] | |
| 316 | pub 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] | |
| 325 | pub 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] | |
| 335 | pub 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. | 345 | pub 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-7nm8r9 | 350 | #[cfg(test)] |
| 351 | mod 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-agents | 362 | |
| 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 work | 374 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.