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 | |
| Merge branch 'main' into rm-agents | 3 | // src/bridge.rs. The settings usually open over the site, which draws |
| 4 | // them (apps/web components/desktop-settings.tsx); this window is for when | |
| 5 | // the window shows no page of the site's. The runner has a window of its | |
| 6 | // own (runner.html); here it is a line that opens it. A button that asks | |
| 7 | // the app for something is busy until it is done (busy.js). | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 8 | (function () { |
| 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. | 9 | var SWITCHES = ["startHidden", "closeToTray", "notifications", "flash", "badge", "systemTitleBar", "autoUpdate"]; |
| 10 | // The settings the app keeps (src/settings.rs Settings); a save sends them all. | |
| 11 | var KEPT = SWITCHES.concat(["zoom", "runner", "runnerBackend", "runnerWorkspace", "runnerCapabilities"]); | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 12 | var current = null; |
| 13 | ||
| 14 | function byId(id) { | |
| 15 | return document.getElementById(id); | |
| 16 | } | |
| 17 | ||
| 18 | function invoke(command, args) { | |
| 19 | var core = window.__TAURI__ && window.__TAURI__.core; | |
| 20 | if (core && typeof core.invoke === "function") return core.invoke(command, args || {}); | |
| 21 | return Promise.reject(new Error("This page belongs inside the g1t desktop app.")); | |
| 22 | } | |
| 23 | ||
| 24 | function fail(error) { | |
| 25 | var box = byId("error"); | |
| 26 | box.textContent = typeof error === "string" ? error : (error && error.message) || "That could not be changed."; | |
| 27 | box.hidden = false; | |
| 28 | } | |
| 29 | ||
| 30 | function hostOf(server) { | |
| 31 | try { | |
| 32 | return new URL(server).host; | |
| 33 | } catch (error) { | |
| 34 | return server; | |
| 35 | } | |
| 36 | } | |
| 37 | ||
| 38 | // What changes between Windows, macOS and Linux. | |
| 39 | function platform(name) { | |
| 40 | byId("flash-label").textContent = | |
| 41 | name === "macos" ? "Bounce the dock icon" : name === "windows" ? "Flash the taskbar icon" : "Ask for attention"; | |
| 42 | // macOS keeps its menu bar at the top of the screen, whichever title bar the window has. | |
| 43 | if (name === "macos") byId("system-title-bar-note").textContent = "In place of g1t's own, which sits under the window's buttons."; | |
| 44 | if (name === "macos") byId("zoom-note").textContent = "The View menu changes it too: ⌘+, ⌘− and ⌘0."; | |
| 45 | else byId("zoom-note").textContent = "The View menu changes it too: Ctrl+=, Ctrl+− and Ctrl+0."; | |
| 46 | } | |
| 47 | ||
| 48 | function render(view) { | |
| 49 | current = view; | |
| 50 | byId("error").hidden = true; | |
| 51 | byId("openAtLogin").checked = !!view.openAtLogin; | |
| 52 | SWITCHES.forEach(function (key) { | |
| 53 | byId(key).checked = !!view[key]; | |
| 54 | }); | |
| 55 | // Starting in the tray only means something when g1t starts at sign-in. | |
| 56 | byId("startHidden").disabled = !view.openAtLogin; | |
| 57 | byId("startHidden").closest(".setting").classList.toggle("off", !view.openAtLogin); | |
| 58 | // The title bar changes when g1t restarts. | |
| 59 | byId("restart-row").hidden = !view.titleBarChanged; | |
| 60 | var zoom = byId("zoom"); | |
| 61 | zoom.value = String(view.zoom); | |
| 62 | if (zoom.value === "") zoom.value = "1"; | |
| 63 | byId("version").textContent = "g1t " + view.version; | |
| 64 | if (!checking) updateStatus(view); | |
| 65 | 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. | 66 | renderRunner(view.runnerView); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 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, in a line: what it is doing, and the window that has the rest. |
| 70 | function renderRunner(runner) { | |
| 71 | if (!runner) return; | |
| 72 | byId("runner-dot").className = "dot " + runner.chip.tone; | |
| 73 | byId("runner-title").textContent = runner.chip.label; | |
| 74 | byId("runner-summary").textContent = runner.diagnosis.title + (runner.version ? " · g1t-runner " + runner.version : ""); | |
| 75 | } | |
| 76 | ||
| 77 | // Every setting, as the app has it now (the Runner window changes the | |
| 78 | // runner's), with this window's own as they are on the page. | |
| 79 | function settingsOf(fresh) { | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 80 | var settings = {}; |
| 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. | 81 | KEPT.forEach(function (key) { |
| 82 | if (key in fresh) settings[key] = fresh[key]; | |
| 83 | }); | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 84 | SWITCHES.forEach(function (key) { |
| 85 | settings[key] = byId(key).checked; | |
| 86 | }); | |
| 87 | settings.zoom = Number(byId("zoom").value) || 1; | |
| 88 | return settings; | |
| 89 | } | |
| 90 | ||
| 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. | 91 | |
| 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. | 92 | function save() { |
| 93 | invoke("get_settings") | |
| 94 | .then(function (fresh) { | |
| 95 | return invoke("save_settings", { settings: settingsOf(fresh) }); | |
| 96 | }) | |
| 97 | .then(render, function (error) { | |
| 98 | fail(error); | |
| 99 | if (current) render(current); | |
| 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. | 100 | }); |
| 101 | } | |
| 102 | ||
| 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. | 103 | byId("open-runner").addEventListener("click", function () { |
| Merge branch 'main' into rm-agents | 104 | window.g1tBusy.run(byId("open-runner"), "open-runner", "", false, function () { |
| 105 | return invoke("runner_control", { action: "open_runner" }); | |
| 106 | }, fail); | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 107 | }); |
| 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. | 108 | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 109 | SWITCHES.forEach(function (key) { |
| 110 | byId(key).addEventListener("change", save); | |
| 111 | }); | |
| 112 | byId("zoom").addEventListener("change", save); | |
| 113 | byId("openAtLogin").addEventListener("change", function (event) { | |
| 114 | invoke("set_open_at_login", { on: event.target.checked }).then(render, function (error) { | |
| 115 | fail(error); | |
| 116 | if (current) render(current); | |
| 117 | }); | |
| 118 | }); | |
| 119 | ||
| 120 | // Updates: where this copy stands, said in the window, so a check from | |
| 121 | // the menu or the tray is answered here and cannot be missed. | |
| 122 | var checking = false; | |
| 123 | ||
| 124 | function when(ms) { | |
| 125 | var date = new Date(ms); | |
| 126 | var today = new Date().toDateString() === date.toDateString(); | |
| 127 | var time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }); | |
| 128 | return today ? "today at " + time : date.toLocaleDateString([], { month: "short", day: "numeric" }) + " at " + time; | |
| 129 | } | |
| 130 | ||
| 131 | function ready(version) { | |
| 132 | byId("update-status").textContent = "Version " + version + " is downloaded and installs when g1t restarts."; | |
| 133 | byId("restart").hidden = false; | |
| 134 | } | |
| 135 | ||
| 136 | function updateStatus(view) { | |
| 137 | if (view.updateReady) return ready(view.updateReady); | |
| 138 | byId("restart").hidden = true; | |
| 139 | byId("update-status").textContent = view.checkedAt | |
| 140 | ? "Up to date. Last checked " + when(view.checkedAt) + "." | |
| 141 | : view.autoUpdate | |
| 142 | ? "Not checked yet since g1t started; it checks on its own shortly." | |
| 143 | : "Checking on its own is off."; | |
| 144 | } | |
| 145 | ||
| 146 | function check() { | |
| 147 | if (checking) return; | |
| 148 | checking = true; | |
| 149 | var button = byId("check"); | |
| 150 | var status = byId("update-status"); | |
| Merge branch 'main' into rm-agents | 151 | window.g1tBusy.start(button); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 152 | status.textContent = "Checking, and downloading any new version…"; |
| 153 | invoke("check_for_updates") | |
| 154 | .then(function (answer) { | |
| 155 | if (answer.indexOf("ready:") === 0) ready(answer.slice(6)); | |
| 156 | else status.textContent = "Up to date. Last checked " + when(Date.now()) + "."; | |
| 157 | }) | |
| 158 | .catch(function (error) { | |
| 159 | var text = typeof error === "string" ? error : ""; | |
| 160 | status.textContent = /sending request|connect|dns|timed out/i.test(text) || !text | |
| 161 | ? "Could not reach g1t.sh to check. Check your connection and try again." | |
| 162 | : "Could not check: " + text; | |
| 163 | }) | |
| 164 | .then(function () { | |
| 165 | checking = false; | |
| Merge branch 'main' into rm-agents | 166 | window.g1tBusy.stop(button); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 167 | }); |
| 168 | } | |
| 169 | ||
| 170 | byId("check").addEventListener("click", check); | |
| 171 | byId("restart").addEventListener("click", function () { | |
| Merge branch 'main' into rm-agents | 172 | window.g1tBusy.start(byId("restart")); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 173 | byId("update-status").textContent = "Installing…"; |
| 174 | invoke("restart_to_update").catch(function (error) { | |
| Merge branch 'main' into rm-agents | 175 | window.g1tBusy.stop(byId("restart")); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 176 | fail(error); |
| 177 | }); | |
| 178 | }); | |
| 179 | ||
| 180 | // The menu's or tray's "Check for updates…" with this window open, and a | |
| 181 | // download finishing in the background (src/settings.rs). | |
| 182 | var events = window.__TAURI__ && window.__TAURI__.event; | |
| 183 | if (events && typeof events.listen === "function") { | |
| 184 | events.listen("g1t-check-updates", check).catch(function () {}); | |
| 185 | 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. | 186 | .listen("g1t-runner-status", function (event) { |
| 187 | if (current) current.runnerView = event.payload; | |
| 188 | renderRunner(event.payload); | |
| 189 | }) | |
| 190 | .catch(function () {}); | |
| 191 | events | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 192 | .listen("g1t-update-ready", function (event) { |
| 193 | if (!checking && typeof event.payload === "string") ready(event.payload); | |
| 194 | }) | |
| 195 | .catch(function () {}); | |
| 196 | } | |
| 197 | ||
| 198 | byId("restart-app").addEventListener("click", function () { | |
| Merge branch 'main' into rm-agents | 199 | window.g1tBusy.start(byId("restart-app")); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 200 | invoke("restart").catch(function (error) { |
| Merge branch 'main' into rm-agents | 201 | window.g1tBusy.stop(byId("restart-app")); |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 202 | fail(error); |
| 203 | }); | |
| 204 | }); | |
| 205 | ||
| 206 | byId("change-server").addEventListener("click", function () { | |
| Merge branch 'main' into rm-agents | 207 | window.g1tBusy.run(byId("change-server"), "change-server", "", false, function () { |
| 208 | return invoke("change_server"); | |
| 209 | }, fail); | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 210 | }); |
| 211 | ||
| 212 | // Links out, in the system's browser (src/bridge.js does the same on the site). | |
| 213 | document.addEventListener("click", function (event) { | |
| 214 | var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null; | |
| 215 | if (!anchor) return; | |
| 216 | event.preventDefault(); | |
| 217 | invoke("open_url", { url: anchor.href }).catch(function () {}); | |
| 218 | }); | |
| 219 | ||
| 220 | // g1t's own title bar, when the window has no system frame. | |
| 221 | function frame(view) { | |
| 222 | if (!view.customFrame) return; | |
| 223 | if (view.platform === "macos") document.documentElement.classList.add("mac"); | |
| 224 | var bar = byId("titlebar"); | |
| 225 | bar.hidden = false; | |
| 226 | document.body.classList.add("framed"); | |
| 227 | bar.addEventListener("mousedown", function (event) { | |
| 228 | if (event.button !== 0 || event.target.closest("button")) return; | |
| 229 | invoke("window_control", { action: "drag" }).catch(function () {}); | |
| 230 | }); | |
| 231 | byId("close-window").addEventListener("click", function () { | |
| 232 | invoke("window_control", { action: "close" }).catch(function () {}); | |
| 233 | }); | |
| 234 | } | |
| 235 | ||
| 236 | invoke("get_settings") | |
| 237 | .then(function (view) { | |
| 238 | frame(view); | |
| 239 | platform(view.platform); | |
| 240 | render(view); | |
| 241 | // Opened by "Check for updates…" in a menu. | |
| 242 | if (new URLSearchParams(location.search).has("check")) { | |
| 243 | byId("updates-row").scrollIntoView({ block: "center" }); | |
| 244 | check(); | |
| 245 | } | |
| 246 | }) | |
| 247 | .catch(fail); | |
| 248 | })(); |
This file's history is long; its oldest lines are credited to the oldest commit read.