Skip to content
349 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.

Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91// 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-7nm8r918 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-7nm8r973 }
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-7nm8r983 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-7nm8r9205 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-7nm8r9295 .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.