Skip to content
319 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 remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9109 // The app's own settings, in a window of their own.
110 openSettings: function () {
111 quietly(invoke("open_settings"));
112 },
113 // The title bar's search, in its middle: the site hands it a label
114 // ("Search Flagon") and what opens its search. Returns what takes it away.
115 setSearch: function (label, open) {
116 var mine = typeof open === "function" ? { label: String(label || "Search"), open: open } : null;
117 search = mine;
118 report();
119 return function () {
120 if (search === mine) {
121 search = null;
122 report();
123 }
124 };
125 },
126 // Whether g1t draws the title bar, and how tall it is: 0 under the system's.
127 // The page is below it, never under it: this says only that the bar is there.
128 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.129 // The runner the app keeps on this computer (src/runner/mod.rs).
130 runner: Object.freeze({
131 // What it is doing: its diagnosis (why it isn't running, and the
132 // fix), the workspace, how jobs run, and its last log lines.
133 status: function () {
134 return invoke("runner_status");
135 },
136 // Who is signed in, the workspaces they own, and the one they are in;
137 // resolves to the workspace a registration token is wanted for, or null.
138 offer: function (offer) {
139 return invoke("runner_offer", {
140 signedIn: !!offer.signedIn,
141 workspaces: (offer.workspaces || []).map(function (w) {
142 return { slug: String(w.slug), name: w.name == null ? null : String(w.name) };
143 }),
144 current: offer.current == null ? null : String(offer.current),
145 });
146 },
147 // A registration token for the workspace the app asked for, or why there is none.
148 register: function (answer) {
149 return invoke("runner_register", {
150 workspace: String(answer.workspace),
151 token: answer.token == null ? null : String(answer.token),
152 allowed: answer.allowed !== false,
153 error: answer.error == null ? null : String(answer.error),
154 });
155 },
156 // Start, stop, or a diagnosis's fix, by its id.
157 act: function (action) {
158 return invoke("runner_control", { action: String(action) });
159 },
160 // The app wants a registration token for a workspace.
161 onWanted: function (listener) {
162 return on("g1t-runner-wanted", listener);
163 },
164 // The runner's status changed.
165 onStatus: function (listener) {
166 return on("g1t-runner-status", listener);
167 },
168 }),
preparing for cloud work169 };
170 Object.defineProperty(window, "g1tDesktop", { value: Object.freeze(bridge), writable: false, configurable: false });
171
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9172 // Links out. A link to another site, or to one of the site's pages for
173 // reading about g1t, opens in the system's browser, and the window stays
174 // where it is; a link that asks for a new tab, or a click
preparing for cloud work175 // 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-7nm8r9176 function outside(url) {
177 var path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, "") : url.pathname;
178 return OUTSIDE.some(function (page) {
179 return page.charAt(page.length - 1) === "/" ? path.indexOf(page) === 0 : path === page;
180 });
181 }
preparing for cloud work182 function destination(href) {
183 try {
184 var url = new URL(href, location.href);
185 return /^https?:$/.test(url.protocol) ? url : null;
186 } catch (error) {
187 return null;
188 }
189 }
190 document.addEventListener(
191 "click",
192 function (event) {
193 if (event.defaultPrevented || event.button !== 0) return;
194 var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
195 if (!anchor || anchor.hasAttribute("download")) return;
196 var url = destination(anchor.getAttribute("href"));
197 if (!url) return;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9198 if (url.origin !== location.origin || outside(url)) {
preparing for cloud work199 event.preventDefault();
200 event.stopImmediatePropagation();
201 bridge.openUrl(url.href);
202 } else if (anchor.target === "_blank" || event.metaKey || event.ctrlKey) {
203 event.preventDefault();
204 event.stopImmediatePropagation();
205 location.assign(url.href);
206 }
207 },
208 true,
209 );
210 var open = window.open;
211 window.open = function (href, target, features) {
212 var url = destination(href);
213 if (url) {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9214 if (url.origin === location.origin && !outside(url)) location.assign(url.href);
preparing for cloud work215 else bridge.openUrl(url.href);
216 return null;
217 }
218 return typeof open === "function" ? open.call(window, href, target, features) : null;
219 };
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9220
221 // ── The title bar ─────────────────────────────────────────────────────
222 // What the bar above the page shows of it: the title, the search the
223 // site handed over (`setSearch`), whether there is a page to go back or
224 // forward to, and the page's colours, so the bar is the colour of g1t's
225 // rail, light or dark, or of whatever page is showing. Sent whenever any
226 // of it changes; the bar's search comes back as an event.
227 var search = null;
228 var report = function () {};
229 if (FRAME !== "custom") return;
230
231 // A colour the page names, resolved: `var(--color-rail)` is light-dark(…) until it is drawn.
232 var probe = null;
233 function resolve(value) {
234 if (!document.body) return "";
235 if (!probe) {
236 probe = document.createElement("span");
237 probe.setAttribute("aria-hidden", "true");
238 probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;";
239 }
240 if (probe.parentNode !== document.body) document.body.appendChild(probe);
241 probe.style.color = "";
242 probe.style.color = value;
243 var color = getComputedStyle(probe).color;
244 probe.remove();
245 return color;
246 }
247 function opaque(color) {
248 return !!color && color !== "transparent" && !/rgba\(.*,\s*0\)$/.test(color);
249 }
250 function luminance(color) {
251 var parts = (color.match(/[\d.]+/g) || []).map(Number);
252 return parts.length < 3 ? 0 : (0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]) / 255;
253 }
254 // g1t's pages name their colours (apps/web/app/app.css); any other page has its own background and text.
255 function colors() {
256 if (!document.body) return null;
257 var root = getComputedStyle(document.documentElement);
258 var ours = root.getPropertyValue("--color-rail").trim() !== "";
259 var body = getComputedStyle(document.body);
260 var background = ours ? resolve("var(--color-rail)") : body.backgroundColor;
261 if (!opaque(background)) background = root.backgroundColor;
262 if (!opaque(background)) return null;
263 var foreground = ours ? resolve("var(--color-fg)") : body.color;
264 return {
265 background: background,
266 foreground: foreground,
267 muted: ours ? resolve("var(--color-muted)") : foreground,
268 accent: ours ? resolve("var(--color-accent)") : foreground,
269 dark: luminance(background) < 0.5,
270 };
271 }
272
273 var sent = "";
274 var waiting = 0;
275 report = function () {
276 clearTimeout(waiting);
277 waiting = setTimeout(function () {
278 var nav = window.navigation;
279 var page = {
280 title: document.title || "g1t",
281 search: search ? search.label : null,
282 canGoBack: nav && typeof nav.canGoBack === "boolean" ? nav.canGoBack : null,
283 canGoForward: nav && typeof nav.canGoForward === "boolean" ? nav.canGoForward : null,
284 colors: colors(),
285 };
286 var text = JSON.stringify(page);
287 if (text === sent) return;
288 sent = text;
289 invoke("title_bar_page", { page: page }).catch(function () {});
290 }, 30);
291 };
292
293 document.addEventListener("DOMContentLoaded", function () {
294 report();
295 if (document.head) new MutationObserver(report).observe(document.head, { subtree: true, childList: true, characterData: true });
296 // The site's Light, Dark or Auto (data-theme on <html>), and the system's own.
297 new MutationObserver(report).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class", "style"] });
298 });
299 window.addEventListener("load", report);
300 window.addEventListener("pageshow", report);
301 window.addEventListener("popstate", report);
302 if (window.matchMedia) {
303 var scheme = window.matchMedia("(prefers-color-scheme: dark)");
304 if (typeof scheme.addEventListener === "function") scheme.addEventListener("change", report);
305 }
306 if (window.navigation && typeof window.navigation.addEventListener === "function") {
307 window.navigation.addEventListener("currententrychange", report);
308 }
309
310 // The bar's search was clicked: the site's search opens.
311 var events = window.__TAURI__ && window.__TAURI__.event;
312 if (events && typeof events.listen === "function") {
313 events
314 .listen("g1t-search", function () {
315 if (search) search.open();
316 })
317 .catch(function () {});
318 }
preparing for cloud work319})();

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