Skip to content
254 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
56 var bridge = {
57 version: VERSION,
58 platform: PLATFORM,
59 // A native notification, for a message to the person while the window is behind another.
60 notify: function (notification) {
61 quietly(invoke("notify", { title: String(notification.title || "g1t"), body: String(notification.body || "") }));
62 },
63 // What needs the person, on the icon and the tray; 0 clears it.
64 setBadge: function (count) {
65 quietly(invoke("set_badge", { count: Math.max(0, Math.floor(Number(count) || 0)) }));
66 },
67 // A link to somewhere else, in the browser the system uses.
68 openUrl: function (url) {
69 quietly(invoke("open_url", { url: String(url) }));
70 },
71 // g1t:// links, as site paths such as "/acme/-/chat/dm/chn_1".
72 onNavigate: function (listener) {
73 listeners.push(listener);
74 listen();
75 return function () {
76 var at = listeners.indexOf(listener);
77 if (at >= 0) listeners.splice(at, 1);
78 };
79 },
80 // Looks for an update now: "current", "ready:<version>", or rejects with why.
81 checkForUpdates: function () {
82 return invoke("check_for_updates");
83 },
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r984 // The app's own settings, in a window of their own.
85 openSettings: function () {
86 quietly(invoke("open_settings"));
87 },
88 // The title bar's search, in its middle: the site hands it a label
89 // ("Search Flagon") and what opens its search. Returns what takes it away.
90 setSearch: function (label, open) {
91 var mine = typeof open === "function" ? { label: String(label || "Search"), open: open } : null;
92 search = mine;
93 report();
94 return function () {
95 if (search === mine) {
96 search = null;
97 report();
98 }
99 };
100 },
101 // Whether g1t draws the title bar, and how tall it is: 0 under the system's.
102 // The page is below it, never under it: this says only that the bar is there.
103 titleBarHeight: FRAME === "custom" ? 36 : 0,
preparing for cloud work104 };
105 Object.defineProperty(window, "g1tDesktop", { value: Object.freeze(bridge), writable: false, configurable: false });
106
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9107 // Links out. A link to another site, or to one of the site's pages for
108 // reading about g1t, opens in the system's browser, and the window stays
109 // where it is; a link that asks for a new tab, or a click
preparing for cloud work110 // 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-7nm8r9111 function outside(url) {
112 var path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, "") : url.pathname;
113 return OUTSIDE.some(function (page) {
114 return page.charAt(page.length - 1) === "/" ? path.indexOf(page) === 0 : path === page;
115 });
116 }
preparing for cloud work117 function destination(href) {
118 try {
119 var url = new URL(href, location.href);
120 return /^https?:$/.test(url.protocol) ? url : null;
121 } catch (error) {
122 return null;
123 }
124 }
125 document.addEventListener(
126 "click",
127 function (event) {
128 if (event.defaultPrevented || event.button !== 0) return;
129 var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
130 if (!anchor || anchor.hasAttribute("download")) return;
131 var url = destination(anchor.getAttribute("href"));
132 if (!url) return;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9133 if (url.origin !== location.origin || outside(url)) {
preparing for cloud work134 event.preventDefault();
135 event.stopImmediatePropagation();
136 bridge.openUrl(url.href);
137 } else if (anchor.target === "_blank" || event.metaKey || event.ctrlKey) {
138 event.preventDefault();
139 event.stopImmediatePropagation();
140 location.assign(url.href);
141 }
142 },
143 true,
144 );
145 var open = window.open;
146 window.open = function (href, target, features) {
147 var url = destination(href);
148 if (url) {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9149 if (url.origin === location.origin && !outside(url)) location.assign(url.href);
preparing for cloud work150 else bridge.openUrl(url.href);
151 return null;
152 }
153 return typeof open === "function" ? open.call(window, href, target, features) : null;
154 };
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9155
156 // ── The title bar ─────────────────────────────────────────────────────
157 // What the bar above the page shows of it: the title, the search the
158 // site handed over (`setSearch`), whether there is a page to go back or
159 // forward to, and the page's colours, so the bar is the colour of g1t's
160 // rail, light or dark, or of whatever page is showing. Sent whenever any
161 // of it changes; the bar's search comes back as an event.
162 var search = null;
163 var report = function () {};
164 if (FRAME !== "custom") return;
165
166 // A colour the page names, resolved: `var(--color-rail)` is light-dark(…) until it is drawn.
167 var probe = null;
168 function resolve(value) {
169 if (!document.body) return "";
170 if (!probe) {
171 probe = document.createElement("span");
172 probe.setAttribute("aria-hidden", "true");
173 probe.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;";
174 }
175 if (probe.parentNode !== document.body) document.body.appendChild(probe);
176 probe.style.color = "";
177 probe.style.color = value;
178 var color = getComputedStyle(probe).color;
179 probe.remove();
180 return color;
181 }
182 function opaque(color) {
183 return !!color && color !== "transparent" && !/rgba\(.*,\s*0\)$/.test(color);
184 }
185 function luminance(color) {
186 var parts = (color.match(/[\d.]+/g) || []).map(Number);
187 return parts.length < 3 ? 0 : (0.2126 * parts[0] + 0.7152 * parts[1] + 0.0722 * parts[2]) / 255;
188 }
189 // g1t's pages name their colours (apps/web/app/app.css); any other page has its own background and text.
190 function colors() {
191 if (!document.body) return null;
192 var root = getComputedStyle(document.documentElement);
193 var ours = root.getPropertyValue("--color-rail").trim() !== "";
194 var body = getComputedStyle(document.body);
195 var background = ours ? resolve("var(--color-rail)") : body.backgroundColor;
196 if (!opaque(background)) background = root.backgroundColor;
197 if (!opaque(background)) return null;
198 var foreground = ours ? resolve("var(--color-fg)") : body.color;
199 return {
200 background: background,
201 foreground: foreground,
202 muted: ours ? resolve("var(--color-muted)") : foreground,
203 accent: ours ? resolve("var(--color-accent)") : foreground,
204 dark: luminance(background) < 0.5,
205 };
206 }
207
208 var sent = "";
209 var waiting = 0;
210 report = function () {
211 clearTimeout(waiting);
212 waiting = setTimeout(function () {
213 var nav = window.navigation;
214 var page = {
215 title: document.title || "g1t",
216 search: search ? search.label : null,
217 canGoBack: nav && typeof nav.canGoBack === "boolean" ? nav.canGoBack : null,
218 canGoForward: nav && typeof nav.canGoForward === "boolean" ? nav.canGoForward : null,
219 colors: colors(),
220 };
221 var text = JSON.stringify(page);
222 if (text === sent) return;
223 sent = text;
224 invoke("title_bar_page", { page: page }).catch(function () {});
225 }, 30);
226 };
227
228 document.addEventListener("DOMContentLoaded", function () {
229 report();
230 if (document.head) new MutationObserver(report).observe(document.head, { subtree: true, childList: true, characterData: true });
231 // The site's Light, Dark or Auto (data-theme on <html>), and the system's own.
232 new MutationObserver(report).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "class", "style"] });
233 });
234 window.addEventListener("load", report);
235 window.addEventListener("pageshow", report);
236 window.addEventListener("popstate", report);
237 if (window.matchMedia) {
238 var scheme = window.matchMedia("(prefers-color-scheme: dark)");
239 if (typeof scheme.addEventListener === "function") scheme.addEventListener("change", report);
240 }
241 if (window.navigation && typeof window.navigation.addEventListener === "function") {
242 window.navigation.addEventListener("currententrychange", report);
243 }
244
245 // The bar's search was clicked: the site's search opens.
246 var events = window.__TAURI__ && window.__TAURI__.event;
247 if (events && typeof events.listen === "function") {
248 events
249 .listen("g1t-search", function () {
250 if (search) search.open();
251 })
252 .catch(function () {});
253 }
preparing for cloud work254})();

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