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 work | 1 | // 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-7nm8r9 | 12 | // 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 work | 19 | |
| 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 work | 81 | 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-7nm8r9 | 109 | // 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 work | 169 | }; |
| 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-7nm8r9 | 172 | // 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 work | 175 | // 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-7nm8r9 | 176 | 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 work | 182 | 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-7nm8r9 | 198 | if (url.origin !== location.origin || outside(url)) { |
| preparing for cloud work | 199 | 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-7nm8r9 | 214 | if (url.origin === location.origin && !outside(url)) location.assign(url.href); |
| preparing for cloud work | 215 | 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-7nm8r9 | 220 | |
| 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 work | 319 | })(); |
This file's history is long; its oldest lines are credited to the oldest commit read.