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.
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 1 | // The settings window's work: show what the app has, and keep each change |
| 2 | // the moment it is made. Everything it asks is a command in | |
| 3 | // src/bridge.rs, answered only to this window. | |
| 4 | (function () { | |
| 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. | 5 | var SWITCHES = ["startHidden", "closeToTray", "notifications", "flash", "badge", "systemTitleBar", "autoUpdate", "runner"]; |
| 6 | // What each way of running jobs can reach, said plainly (src/runner/backend.rs). | |
| 7 | var BACKENDS = { | |
| 8 | sandboxed: { text: "The runner and every job run in a sandbox of their own. Nothing a job does reaches this computer.", warn: false }, | |
| 9 | docker: { | |
| 10 | text: "Each job runs in a container of its own, but the runner controls this computer's Docker, which can reach your files. Not a sandbox.", | |
| 11 | warn: true, | |
| 12 | }, | |
| 13 | host: { | |
| 14 | text: "Jobs run directly on this computer, as you: they can read, change and delete anything your account can. For jobs that need this system itself, such as building a Windows installer.", | |
| 15 | warn: true, | |
| 16 | }, | |
| 17 | }; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 18 | var current = null; |
| 19 | ||
| 20 | function byId(id) { | |
| 21 | return document.getElementById(id); | |
| 22 | } | |
| 23 | ||
| 24 | function invoke(command, args) { | |
| 25 | var core = window.__TAURI__ && window.__TAURI__.core; | |
| 26 | if (core && typeof core.invoke === "function") return core.invoke(command, args || {}); | |
| 27 | return Promise.reject(new Error("This page belongs inside the g1t desktop app.")); | |
| 28 | } | |
| 29 | ||
| 30 | function fail(error) { | |
| 31 | var box = byId("error"); | |
| 32 | box.textContent = typeof error === "string" ? error : (error && error.message) || "That could not be changed."; | |
| 33 | box.hidden = false; | |
| 34 | } | |
| 35 | ||
| 36 | function hostOf(server) { | |
| 37 | try { | |
| 38 | return new URL(server).host; | |
| 39 | } catch (error) { | |
| 40 | return server; | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | // What changes between Windows, macOS and Linux. | |
| 45 | function platform(name) { | |
| 46 | byId("flash-label").textContent = | |
| 47 | name === "macos" ? "Bounce the dock icon" : name === "windows" ? "Flash the taskbar icon" : "Ask for attention"; | |
| 48 | // macOS keeps its menu bar at the top of the screen, whichever title bar the window has. | |
| 49 | if (name === "macos") byId("system-title-bar-note").textContent = "In place of g1t's own, which sits under the window's buttons."; | |
| 50 | if (name === "macos") byId("zoom-note").textContent = "The View menu changes it too: ⌘+, ⌘− and ⌘0."; | |
| 51 | else byId("zoom-note").textContent = "The View menu changes it too: Ctrl+=, Ctrl+− and Ctrl+0."; | |
| 52 | } | |
| 53 | ||
| 54 | function render(view) { | |
| 55 | current = view; | |
| 56 | byId("error").hidden = true; | |
| 57 | byId("openAtLogin").checked = !!view.openAtLogin; | |
| 58 | SWITCHES.forEach(function (key) { | |
| 59 | byId(key).checked = !!view[key]; | |
| 60 | }); | |
| 61 | // Starting in the tray only means something when g1t starts at sign-in. | |
| 62 | byId("startHidden").disabled = !view.openAtLogin; | |
| 63 | byId("startHidden").closest(".setting").classList.toggle("off", !view.openAtLogin); | |
| 64 | // The title bar changes when g1t restarts. | |
| 65 | byId("restart-row").hidden = !view.titleBarChanged; | |
| 66 | var zoom = byId("zoom"); | |
| 67 | zoom.value = String(view.zoom); | |
| 68 | if (zoom.value === "") zoom.value = "1"; | |
| 69 | byId("version").textContent = "g1t " + view.version; | |
| 70 | if (!checking) updateStatus(view); | |
| 71 | byId("server").textContent = hostOf(view.server); | |
| 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. | 72 | renderRunner(view.runnerView); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 73 | } |
| 74 | ||
| 75 | function settingsOf() { | |
| 76 | var settings = {}; | |
| 77 | SWITCHES.forEach(function (key) { | |
| 78 | settings[key] = byId(key).checked; | |
| 79 | }); | |
| 80 | settings.zoom = Number(byId("zoom").value) || 1; | |
| 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. | 81 | settings.runnerBackend = byId("runnerBackend").value || "sandboxed"; |
| 82 | settings.runnerWorkspace = byId("runnerWorkspace").value || null; | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 83 | return settings; |
| 84 | } | |
| 85 | ||
| 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. | 86 | // ── The runner ────────────────────────────────────────────────────── |
| 87 | // Its diagnosis, from src/runner/diagnosis.rs, as the tray and the | |
| 88 | // site's Runners page show it; its buttons are the fixes. | |
| 89 | function option(value, text) { | |
| 90 | var item = document.createElement("option"); | |
| 91 | item.value = value; | |
| 92 | item.textContent = text; | |
| 93 | return item; | |
| 94 | } | |
| 95 | ||
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 96 | // A fix's button. Choosing is done here, in this window; a copy says it was copied. |
| 97 | function actionButton(action, primary) { | |
| 98 | var button = document.createElement("button"); | |
| 99 | button.type = "button"; | |
| 100 | button.textContent = action.label; | |
| 101 | if (primary) button.className = "primary"; | |
| 102 | button.addEventListener("click", function () { | |
| 103 | if (action.id === "choose_workspace") return byId("runnerWorkspace").focus(); | |
| 104 | if (action.id === "choose_backend") return byId("runnerBackend").focus(); | |
| 105 | invoke("runner_control", { action: action.id }).then(function () { | |
| 106 | if (action.id.indexOf("copy_") !== 0) return; | |
| 107 | button.textContent = "Copied"; | |
| 108 | setTimeout(function () { | |
| 109 | button.textContent = action.label; | |
| 110 | }, 2000); | |
| 111 | }, fail); | |
| 112 | }); | |
| 113 | return button; | |
| 114 | } | |
| 115 | ||
| 116 | function span(className, text) { | |
| 117 | var item = document.createElement("span"); | |
| 118 | if (className) item.className = className; | |
| 119 | item.textContent = text; | |
| 120 | return item; | |
| 121 | } | |
| 122 | ||
| 123 | // Each kind of job the sandbox runs, when there is more than one: Linux | |
| 124 | // jobs can run while Windows jobs cannot. | |
| 125 | function renderCapabilities(capabilities) { | |
| 126 | var list = byId("runner-capabilities"); | |
| 127 | list.textContent = ""; | |
| 128 | list.hidden = !capabilities || capabilities.length < 2; | |
| 129 | if (list.hidden) return; | |
| 130 | capabilities.forEach(function (capability) { | |
| 131 | var item = document.createElement("li"); | |
| 132 | item.appendChild(span("dot " + capability.tone, "")); | |
| 133 | var text = span("diagnosis-text", ""); | |
| 134 | var title = document.createElement("strong"); | |
| 135 | title.textContent = capability.label + ": " + capability.title; | |
| 136 | text.appendChild(title); | |
| 137 | if (capability.cause) text.appendChild(span("muted", capability.cause)); | |
| 138 | if (capability.fix) text.appendChild(span("", capability.fix)); | |
| 139 | var buttons = span("buttons", ""); | |
| 140 | capability.actions.forEach(function (action) { | |
| 141 | buttons.appendChild(actionButton(action, false)); | |
| 142 | }); | |
| 143 | text.appendChild(buttons); | |
| 144 | item.appendChild(text); | |
| 145 | list.appendChild(item); | |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 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. | 149 | function renderRunner(runner) { |
| 150 | if (!runner) return; | |
| 151 | var diagnosis = runner.diagnosis; | |
| 152 | byId("runner-dot").className = "dot " + diagnosis.tone; | |
| 153 | byId("runner-title").textContent = diagnosis.title; | |
| 154 | byId("runner-cause").textContent = diagnosis.cause || ""; | |
| 155 | byId("runner-fix").textContent = diagnosis.fix || ""; | |
| 156 | var actions = byId("runner-actions"); | |
| 157 | actions.textContent = ""; | |
| 158 | diagnosis.actions.forEach(function (action, at) { | |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 159 | actions.appendChild(actionButton(action, at === 0)); |
| 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. | 160 | }); |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 161 | renderCapabilities(diagnosis.capabilities); |
| 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. | 162 | byId("runner").checked = !!runner.enabled; |
| 163 | var on = runner.enabled && !runner.paused; | |
| 164 | byId("runner-toggle").textContent = on ? "Stop" : "Start"; | |
| 165 | byId("runner-toggle").dataset.action = on ? "stop" : "start"; | |
| 166 | ||
| 167 | var backend = byId("runnerBackend"); | |
| 168 | backend.value = runner.backend; | |
| 169 | var about = BACKENDS[runner.backend] || BACKENDS.sandboxed; | |
| 170 | byId("runner-backend-note").textContent = about.text; | |
| 171 | byId("runner-backend-note").className = about.warn ? "warning-note" : "muted"; | |
| 172 | ||
| 173 | var workspaces = byId("runnerWorkspace"); | |
| 174 | workspaces.textContent = ""; | |
| 175 | var known = runner.workspaces.slice(); | |
| 176 | var listed = known.some(function (w) { | |
| 177 | return w.slug === runner.workspace; | |
| 178 | }); | |
| 179 | if (runner.workspace && !listed) known.unshift({ slug: runner.workspace, name: null }); | |
| 180 | known.forEach(function (w) { | |
| 181 | workspaces.appendChild(option(w.slug, w.name ? w.name + " (" + w.slug + ")" : w.slug)); | |
| 182 | }); | |
| 183 | if (known.length === 0) workspaces.appendChild(option("", "Open g1t, signed in, to choose")); | |
| 184 | workspaces.value = runner.workspace || ""; | |
| 185 | workspaces.disabled = known.length === 0; | |
| 186 | ||
| 187 | byId("runner-machine").textContent = "This computer: " + runner.machine; | |
| 188 | byId("runner-version").textContent = runner.version ? "g1t-runner " + runner.version : " "; | |
| 189 | byId("runner-view").hidden = !runner.link; | |
| 190 | byId("runner-log").textContent = runner.log.length ? runner.log.join("\n") : "Nothing yet."; | |
| 191 | } | |
| 192 | ||
| 193 | byId("runnerBackend").addEventListener("change", save); | |
| 194 | byId("runnerWorkspace").addEventListener("change", save); | |
| 195 | byId("runner-toggle").addEventListener("click", function (event) { | |
| 196 | invoke("runner_control", { action: event.target.dataset.action || "start" }).catch(fail); | |
| 197 | }); | |
| 198 | byId("runner-view").addEventListener("click", function () { | |
| 199 | invoke("runner_control", { action: "view_runners" }).catch(fail); | |
| 200 | }); | |
| 201 | byId("runner-open-log").addEventListener("click", function () { | |
| 202 | invoke("runner_control", { action: "view_log" }).catch(fail); | |
| 203 | }); | |
| 204 | ||
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 205 | function save() { |
| 206 | invoke("save_settings", { settings: settingsOf() }).then(render, function (error) { | |
| 207 | fail(error); | |
| 208 | if (current) render(current); | |
| 209 | }); | |
| 210 | } | |
| 211 | ||
| 212 | SWITCHES.forEach(function (key) { | |
| 213 | byId(key).addEventListener("change", save); | |
| 214 | }); | |
| 215 | byId("zoom").addEventListener("change", save); | |
| 216 | byId("openAtLogin").addEventListener("change", function (event) { | |
| 217 | invoke("set_open_at_login", { on: event.target.checked }).then(render, function (error) { | |
| 218 | fail(error); | |
| 219 | if (current) render(current); | |
| 220 | }); | |
| 221 | }); | |
| 222 | ||
| 223 | // Updates: where this copy stands, said in the window, so a check from | |
| 224 | // the menu or the tray is answered here and cannot be missed. | |
| 225 | var checking = false; | |
| 226 | ||
| 227 | function when(ms) { | |
| 228 | var date = new Date(ms); | |
| 229 | var today = new Date().toDateString() === date.toDateString(); | |
| 230 | var time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }); | |
| 231 | return today ? "today at " + time : date.toLocaleDateString([], { month: "short", day: "numeric" }) + " at " + time; | |
| 232 | } | |
| 233 | ||
| 234 | function ready(version) { | |
| 235 | byId("update-status").textContent = "Version " + version + " is downloaded and installs when g1t restarts."; | |
| 236 | byId("restart").hidden = false; | |
| 237 | } | |
| 238 | ||
| 239 | function updateStatus(view) { | |
| 240 | if (view.updateReady) return ready(view.updateReady); | |
| 241 | byId("restart").hidden = true; | |
| 242 | byId("update-status").textContent = view.checkedAt | |
| 243 | ? "Up to date. Last checked " + when(view.checkedAt) + "." | |
| 244 | : view.autoUpdate | |
| 245 | ? "Not checked yet since g1t started; it checks on its own shortly." | |
| 246 | : "Checking on its own is off."; | |
| 247 | } | |
| 248 | ||
| 249 | function check() { | |
| 250 | if (checking) return; | |
| 251 | checking = true; | |
| 252 | var button = byId("check"); | |
| 253 | var status = byId("update-status"); | |
| 254 | button.disabled = true; | |
| 255 | status.textContent = "Checking, and downloading any new version…"; | |
| 256 | invoke("check_for_updates") | |
| 257 | .then(function (answer) { | |
| 258 | if (answer.indexOf("ready:") === 0) ready(answer.slice(6)); | |
| 259 | else status.textContent = "Up to date. Last checked " + when(Date.now()) + "."; | |
| 260 | }) | |
| 261 | .catch(function (error) { | |
| 262 | var text = typeof error === "string" ? error : ""; | |
| 263 | status.textContent = /sending request|connect|dns|timed out/i.test(text) || !text | |
| 264 | ? "Could not reach g1t.sh to check. Check your connection and try again." | |
| 265 | : "Could not check: " + text; | |
| 266 | }) | |
| 267 | .then(function () { | |
| 268 | checking = false; | |
| 269 | button.disabled = false; | |
| 270 | }); | |
| 271 | } | |
| 272 | ||
| 273 | byId("check").addEventListener("click", check); | |
| 274 | byId("restart").addEventListener("click", function () { | |
| 275 | byId("restart").disabled = true; | |
| 276 | byId("update-status").textContent = "Installing…"; | |
| 277 | invoke("restart_to_update").catch(function (error) { | |
| 278 | byId("restart").disabled = false; | |
| 279 | fail(error); | |
| 280 | }); | |
| 281 | }); | |
| 282 | ||
| 283 | // The menu's or tray's "Check for updates…" with this window open, and a | |
| 284 | // download finishing in the background (src/settings.rs). | |
| 285 | var events = window.__TAURI__ && window.__TAURI__.event; | |
| 286 | if (events && typeof events.listen === "function") { | |
| 287 | events.listen("g1t-check-updates", check).catch(function () {}); | |
| 288 | events | |
| 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. | 289 | .listen("g1t-runner-status", function (event) { |
| 290 | if (current) current.runnerView = event.payload; | |
| 291 | renderRunner(event.payload); | |
| 292 | }) | |
| 293 | .catch(function () {}); | |
| 294 | events | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 295 | .listen("g1t-update-ready", function (event) { |
| 296 | if (!checking && typeof event.payload === "string") ready(event.payload); | |
| 297 | }) | |
| 298 | .catch(function () {}); | |
| 299 | } | |
| 300 | ||
| 301 | byId("restart-app").addEventListener("click", function () { | |
| 302 | byId("restart-app").disabled = true; | |
| 303 | invoke("restart").catch(function (error) { | |
| 304 | byId("restart-app").disabled = false; | |
| 305 | fail(error); | |
| 306 | }); | |
| 307 | }); | |
| 308 | ||
| 309 | byId("change-server").addEventListener("click", function () { | |
| 310 | invoke("change_server").catch(fail); | |
| 311 | }); | |
| 312 | ||
| 313 | // Links out, in the system's browser (src/bridge.js does the same on the site). | |
| 314 | document.addEventListener("click", function (event) { | |
| 315 | var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null; | |
| 316 | if (!anchor) return; | |
| 317 | event.preventDefault(); | |
| 318 | invoke("open_url", { url: anchor.href }).catch(function () {}); | |
| 319 | }); | |
| 320 | ||
| 321 | // g1t's own title bar, when the window has no system frame. | |
| 322 | function frame(view) { | |
| 323 | if (!view.customFrame) return; | |
| 324 | if (view.platform === "macos") document.documentElement.classList.add("mac"); | |
| 325 | var bar = byId("titlebar"); | |
| 326 | bar.hidden = false; | |
| 327 | document.body.classList.add("framed"); | |
| 328 | bar.addEventListener("mousedown", function (event) { | |
| 329 | if (event.button !== 0 || event.target.closest("button")) return; | |
| 330 | invoke("window_control", { action: "drag" }).catch(function () {}); | |
| 331 | }); | |
| 332 | byId("close-window").addEventListener("click", function () { | |
| 333 | invoke("window_control", { action: "close" }).catch(function () {}); | |
| 334 | }); | |
| 335 | } | |
| 336 | ||
| 337 | invoke("get_settings") | |
| 338 | .then(function (view) { | |
| 339 | frame(view); | |
| 340 | platform(view.platform); | |
| 341 | render(view); | |
| 342 | // Opened by "Check for updates…" in a menu. | |
| 343 | if (new URLSearchParams(location.search).has("check")) { | |
| 344 | byId("updates-row").scrollIntoView({ block: "center" }); | |
| 345 | check(); | |
| 346 | } | |
| 347 | }) | |
| 348 | .catch(fail); | |
| 349 | })(); |
This file's history is long; its oldest lines are credited to the oldest commit read.