Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
375 linesCodeBlameRaw

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

preparing for cloud work1// The desktop bridge: what the site finds at `window.g1tDesktop`, run at
2// the start of every page the window loads (bridge.rs puts it there). The
3// site's lib/notify-client.ts describes the contract from its side; when
4// the bridge is there, notifications go to the system, the badge to the
5// icon, links out to the browser, and g1t:// links arrive as `onNavigate`.
6// Each call is a command in bridge.rs, which only answers the server's
7// pages; on any other page the calls fail quietly.
8(function () {
9 if (window.g1tDesktop) return;
10 var VERSION = "__VERSION__";
11 var PLATFORM = "__PLATFORM__";
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r912 // Whether g1t's own title bar is above this page ("custom"), or the
13 // system's ("system"). The bar is a page of its own (src/frame.rs); this
14 // page only tells it what to show (`report`), and is never under it.
15 var FRAME = "__FRAME__";
16 // The site's pages for reading about g1t, not using it, which open in
17 // the browser (navigation.rs); one ending in "/" is every page under it.
18 var OUTSIDE = "__OUTSIDE__";
preparing for cloud work19
20 function invoke(command, args) {
21 var core = window.__TAURI__ && window.__TAURI__.core;
22 if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
23 var internals = window.__TAURI_INTERNALS__;
24 if (internals && typeof internals.invoke === "function") return internals.invoke(command, args || {});
25 return Promise.reject(new Error("g1t desktop: this page cannot reach the app"));
26 }
27
28 function quietly(promise) {
29 promise.catch(function (error) {
30 console.warn("g1t desktop:", error && error.message ? error.message : error);
31 });
32 }
33
34 var listeners = [];
35 var listening = false;
36 function listen() {
37 if (listening) return;
38 listening = true;
39 var events = window.__TAURI__ && window.__TAURI__.event;
40 if (!events || typeof events.listen !== "function") return;
41 events
42 .listen("g1t-navigate", function (event) {
43 var href = event && event.payload;
44 if (typeof href !== "string") return;
45 listeners.slice().forEach(function (listener) {
46 try {
47 listener(href);
48 } catch (error) {
49 console.warn("g1t desktop: a navigate listener failed", error);
50 }
51 });
52 })
53 .catch(function () {});
54 }
55
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.56 // An app event, to a listener; returns what stops listening.
57 function on(name, listener) {
58 var events = window.__TAURI__ && window.__TAURI__.event;
59 if (!events || typeof events.listen !== "function") return function () {};
60 var stop = null;
61 var stopped = false;
62 events
63 .listen(name, function (event) {
64 try {
65 listener(event && event.payload);
66 } catch (error) {
67 console.warn("g1t desktop: a " + name + " listener failed", error);
68 }
69 })
70 .then(function (unlisten) {
71 if (stopped) unlisten();
72 else stop = unlisten;
73 })
74 .catch(function () {});
75 return function () {
76 stopped = true;
77 if (stop) stop();
78 };
79 }
80
preparing for cloud work81 var bridge = {
82 version: VERSION,
83 platform: PLATFORM,
84 // A native notification, for a message to the person while the window is behind another.
85 notify: function (notification) {
86 quietly(invoke("notify", { title: String(notification.title || "g1t"), body: String(notification.body || "") }));
87 },
88 // What needs the person, on the icon and the tray; 0 clears it.
89 setBadge: function (count) {
90 quietly(invoke("set_badge", { count: Math.max(0, Math.floor(Number(count) || 0)) }));
91 },
92 // A link to somewhere else, in the browser the system uses.
93 openUrl: function (url) {
94 quietly(invoke("open_url", { url: String(url) }));
95 },
96 // g1t:// links, as site paths such as "/acme/-/chat/dm/chn_1".
97 onNavigate: function (listener) {
98 listeners.push(listener);
99 listen();
100 return function () {
101 var at = listeners.indexOf(listener);
102 if (at >= 0) listeners.splice(at, 1);
103 };
104 },
105 // Looks for an update now: "current", "ready:<version>", or rejects with why.
106 checkForUpdates: function () {
107 return invoke("check_for_updates");
108 },
Merge the desktop app's settings and runner in the main window: the runner is a dot beside the title bar's Settings button with a menu of its own (its state, each kind of job and what it runs, Start and Stop, updates, the log), Settings opens as a dialog in the window with its sections down the side, the separate windows stay only for when the site isn't showing, and every button that asks the app for something shows it is working until something happens.109 // The app's own settings: the dialog this page draws (`settings.onOpen`),
110 // or their own window when the page can't.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9111 openSettings: function () {
112 quietly(invoke("open_settings"));
113 },
Merge the desktop app's settings and runner in the main window: the runner is a dot beside the title bar's Settings button with a menu of its own (its state, each kind of job and what it runs, Start and Stop, updates, the log), Settings opens as a dialog in the window with its sections down the side, the separate windows stay only for when the site isn't showing, and every button that asks the app for something shows it is working until something happens.114 // The app's settings, for the dialog the site draws over itself, and
115 // the runner's menu under the title bar's dot (src/settings.rs). The
116 // page says it draws them with `ready`; until then they open in windows
117 // of their own.
118 settings: Object.freeze({
119 ready: function (ready) {
120 return invoke("site_ready", { ready: ready !== false });
121 },
122 get: function () {
123 return invoke("get_settings");
124 },
125 // Every setting but opening at sign-in, as `get` has them (camelCase).
126 save: function (settings) {
127 return invoke("save_settings", { settings: settings });
128 },
129 setOpenAtLogin: function (on) {
130 return invoke("set_open_at_login", { on: !!on });
131 },
132 // "current", "ready:<version>", or rejects with why; the dialog says it, so no notification.
133 checkForUpdates: function () {
134 return invoke("check_for_updates", { shown: true });
135 },
136 restart: function () {
137 return invoke("restart");
138 },
139 restartToUpdate: function () {
140 return invoke("restart_to_update");
141 },
142 changeServer: function () {
143 return invoke("change_server");
144 },
145 // Open the dialog: { section: "runner" | "updates" | null, check: boolean }.
146 onOpen: function (listener) {
147 return on("g1t-open-settings", listener);
148 },
149 // The runner menu: { action: "toggle" | "open", at: share of the bar's width | null }.
150 onRunnerMenu: function (listener) {
151 return on("g1t-runner-menu", listener);
152 },
153 // An update finished downloading, with its version.
154 onUpdateReady: function (listener) {
155 return on("g1t-update-ready", listener);
156 },
157 // Whether the runner menu is open, which the title bar's dot shows.
158 setRunnerMenuOpen: function (open) {
159 runnerMenu = !!open;
160 report();
161 },
162 }),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9163 // The title bar's search, in its middle: the site hands it a label
164 // ("Search Flagon") and what opens its search. Returns what takes it away.
165 setSearch: function (label, open) {
166 var mine = typeof open === "function" ? { label: String(label || "Search"), open: open } : null;
167 search = mine;
168 report();
169 return function () {
170 if (search === mine) {
171 search = null;
172 report();
173 }
174 };
175 },
176 // Whether g1t draws the title bar, and how tall it is: 0 under the system's.
177 // The page is below it, never under it: this says only that the bar is there.
178 titleBarHeight: FRAME === "custom" ? 36 : 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.179 // The runner the app keeps on this computer (src/runner/mod.rs).
180 runner: Object.freeze({
181 // What it is doing: its diagnosis (why it isn't running, and the
182 // fix), the workspace, how jobs run, and its last log lines.
183 status: function () {
184 return invoke("runner_status");
185 },
186 // Who is signed in, the workspaces they own, and the one they are in;
187 // resolves to the workspace a registration token is wanted for, or null.
188 offer: function (offer) {
189 return invoke("runner_offer", {
190 signedIn: !!offer.signedIn,
191 workspaces: (offer.workspaces || []).map(function (w) {
192 return { slug: String(w.slug), name: w.name == null ? null : String(w.name) };
193 }),
194 current: offer.current == null ? null : String(offer.current),
195 });
196 },
197 // A registration token for the workspace the app asked for, or why there is none.
198 register: function (answer) {
199 return invoke("runner_register", {
200 workspace: String(answer.workspace),
201 token: answer.token == null ? null : String(answer.token),
202 allowed: answer.allowed !== false,
203 error: answer.error == null ? null : String(answer.error),
204 });
205 },
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.206 // Start, stop, or a diagnosis's fix, by its id; Start and Stop of one
207 // kind of job alone (`linux`, `windows`) with its id as `capability`.
208 act: function (action, capability) {
209 var args = { action: String(action) };
210 if (capability != null) args.capability = String(capability);
211 return invoke("runner_control", args);
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.212 },
213 // The app wants a registration token for a workspace.
214 onWanted: function (listener) {
215 return on("g1t-runner-wanted", listener);
216 },
217 // The runner's status changed.
218 onStatus: function (listener) {
219 return on("g1t-runner-status", listener);
220 },
221 }),
preparing for cloud work222 };
223 Object.defineProperty(window, "g1tDesktop", { value: Object.freeze(bridge), writable: false, configurable: false });
224
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9225 // Links out. A link to another site, or to one of the site's pages for
226 // reading about g1t, opens in the system's browser, and the window stays
227 // where it is; a link that asks for a new tab, or a click
preparing for cloud work228 // with Ctrl or Cmd held, goes on in this window, which has no tabs.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9229 function outside(url) {
230 var path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, "") : url.pathname;
231 return OUTSIDE.some(function (page) {
232 return page.charAt(page.length - 1) === "/" ? path.indexOf(page) === 0 : path === page;
233 });
234 }
preparing for cloud work235 function destination(href) {
236 try {
237 var url = new URL(href, location.href);
238 return /^https?:$/.test(url.protocol) ? url : null;
239 } catch (error) {
240 return null;
241 }
242 }
243 document.addEventListener(
244 "click",
245 function (event) {
246 if (event.defaultPrevented || event.button !== 0) return;
247 var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
248 if (!anchor || anchor.hasAttribute("download")) return;
249 var url = destination(anchor.getAttribute("href"));
250 if (!url) return;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9251 if (url.origin !== location.origin || outside(url)) {
preparing for cloud work252 event.preventDefault();
253 event.stopImmediatePropagation();
254 bridge.openUrl(url.href);
255 } else if (anchor.target === "_blank" || event.metaKey || event.ctrlKey) {
256 event.preventDefault();
257 event.stopImmediatePropagation();
258 location.assign(url.href);
259 }
260 },
261 true,
262 );
263 var open = window.open;
264 window.open = function (href, target, features) {
265 var url = destination(href);
266 if (url) {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9267 if (url.origin === location.origin && !outside(url)) location.assign(url.href);
preparing for cloud work268 else bridge.openUrl(url.href);
269 return null;
270 }
271 return typeof open === "function" ? open.call(window, href, target, features) : null;
272 };
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9273
274 // ── The title bar ─────────────────────────────────────────────────────
275 // What the bar above the page shows of it: the title, the search the
276 // site handed over (`setSearch`), whether there is a page to go back or
Merge the desktop app's settings and runner in the main window: the runner is a dot beside the title bar's Settings button with a menu of its own (its state, each kind of job and what it runs, Start and Stop, updates, the log), Settings opens as a dialog in the window with its sections down the side, the separate windows stay only for when the site isn't showing, and every button that asks the app for something shows it is working until something happens.277 // forward to, the page's colours, so the bar is the colour of g1t's
278 // rail, light or dark, or of whatever page is showing, and whether the
279 // runner menu is open under the bar's dot. Sent whenever any of it
280 // changes; the bar's search comes back as an event.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9281 var search = null;
Merge the desktop app's settings and runner in the main window: the runner is a dot beside the title bar's Settings button with a menu of its own (its state, each kind of job and what it runs, Start and Stop, updates, the log), Settings opens as a dialog in the window with its sections down the side, the separate windows stay only for when the site isn't showing, and every button that asks the app for something shows it is working until something happens.282 var runnerMenu = false;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9283 var report = function () {};
284 if (FRAME !== "custom") return;
285
286 // A colour the page names, resolved: `var(--color-rail)` is light-dark(…) until it is drawn.
287 var probe = null;
288 function resolve(value) {
289 if (!document.body) return "";
290 if (!probe) {
291 probe = document.createElement("span");
292 probe.setAttribute("aria-hidden", "true");
293 probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;";
294 }
295 if (probe.parentNode !== document.body) document.body.appendChild(probe);
296 probe.style.color = "";
297 probe.style.color = value;
298 var color = getComputedStyle(probe).color;
299 probe.remove();
300 return color;
301 }
302 function opaque(color) {
303 return !!color && color !== "transparent" && !/rgba\(.*,\s*0\)$/.test(color);
304 }
305 function luminance(color) {
306 var parts = (color.match(/[\d.]+/g) || []).map(Number);
307 return parts.length < 3 ? 0 : (0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]) / 255;
308 }
309 // g1t's pages name their colours (apps/web/app/app.css); any other page has its own background and text.
310 function colors() {
311 if (!document.body) return null;
312 var root = getComputedStyle(document.documentElement);
313 var ours = root.getPropertyValue("--color-rail").trim() !== "";
314 var body = getComputedStyle(document.body);
315 var background = ours ? resolve("var(--color-rail)") : body.backgroundColor;
316 if (!opaque(background)) background = root.backgroundColor;
317 if (!opaque(background)) return null;
318 var foreground = ours ? resolve("var(--color-fg)") : body.color;
319 return {
320 background: background,
321 foreground: foreground,
322 muted: ours ? resolve("var(--color-muted)") : foreground,
323 accent: ours ? resolve("var(--color-accent)") : foreground,
324 dark: luminance(background) < 0.5,
325 };
326 }
327
328 var sent = "";
329 var waiting = 0;
330 report = function () {
331 clearTimeout(waiting);
332 waiting = setTimeout(function () {
333 var nav = window.navigation;
334 var page = {
335 title: document.title || "g1t",
336 search: search ? search.label : null,
337 canGoBack: nav && typeof nav.canGoBack === "boolean" ? nav.canGoBack : null,
338 canGoForward: nav && typeof nav.canGoForward === "boolean" ? nav.canGoForward : null,
339 colors: colors(),
Merge the desktop app's settings and runner in the main window: the runner is a dot beside the title bar's Settings button with a menu of its own (its state, each kind of job and what it runs, Start and Stop, updates, the log), Settings opens as a dialog in the window with its sections down the side, the separate windows stay only for when the site isn't showing, and every button that asks the app for something shows it is working until something happens.340 runnerMenu: runnerMenu,
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9341 };
342 var text = JSON.stringify(page);
343 if (text === sent) return;
344 sent = text;
345 invoke("title_bar_page", { page: page }).catch(function () {});
346 }, 30);
347 };
348
349 document.addEventListener("DOMContentLoaded", function () {
350 report();
351 if (document.head) new MutationObserver(report).observe(document.head, { subtree: true, childList: true, characterData: true });
352 // The site's Light, Dark or Auto (data-theme on <html>), and the system's own.
353 new MutationObserver(report).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class", "style"] });
354 });
355 window.addEventListener("load", report);
356 window.addEventListener("pageshow", report);
357 window.addEventListener("popstate", report);
358 if (window.matchMedia) {
359 var scheme = window.matchMedia("(prefers-color-scheme: dark)");
360 if (typeof scheme.addEventListener === "function") scheme.addEventListener("change", report);
361 }
362 if (window.navigation && typeof window.navigation.addEventListener === "function") {
363 window.navigation.addEventListener("currententrychange", report);
364 }
365
366 // The bar's search was clicked: the site's search opens.
367 var events = window.__TAURI__ && window.__TAURI__.event;
368 if (events && typeof events.listen === "function") {
369 events
370 .listen("g1t-search", function () {
371 if (search) search.open();
372 })
373 .catch(function () {});
374 }
preparing for cloud work375})();

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