Skip to content

Commit

preparing for cloud work

syntaqxcommitted Parentc284d3cBrowse files
61 files+8307−290/61 viewed
+23−0
9696 run: npm ci --no-audit --no-fund
9797 - name: The site, sudo and the docs
9898 run: node scripts/deploy.mjs build --only web,sudo,docs
99+
100+ desktop:
101+ name: Desktop app
102+ runs-on: ubuntu-latest
103+ timeout-minutes: 30
104+ steps:
105+ - uses: actions/checkout@v5
106+ - name: What Tauri needs on Linux
107+ run: |
108+ sudo apt-get update
109+ sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
110+ - uses: actions/cache@v4
111+ with:
112+ path: |
113+ ~/.cargo/registry
114+ apps/desktop/src-tauri/target
115+ key: desktop-check-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
116+ # Outside the workspace on purpose (Cargo.toml says why): checked and
117+ # tested here, built into installers by desktop-release.yml.
118+ - name: Check and test
119+ run: cargo test --locked --manifest-path apps/desktop/src-tauri/Cargo.toml --quiet
120+ - name: The release script's tests
121+ run: node --test scripts/desktop-release.test.mjs
+139−0
1+# Releases the desktop app, g1t (apps/desktop): builds it for Windows and
2+# Linux on a Linux machine, for macOS on a Mac when one is registered, signs
3+# every updater file, publishes the release to g1t.sh/downloads/desktop/
4+# (the g1t-downloads R2 bucket), and points latest.json at it. Apps already
5+# installed update themselves to it; g1t.sh/download offers it.
6+#
7+# A release is a tag `desktop-v<version>`, where the version is the one in
8+# apps/desktop/src-tauri/Cargo.toml; or run it by hand. scripts/
9+# desktop-release.mjs does the work, and its header says how to make the
10+# signing key the first time; CONTRIBUTING.md ("Deploying") says how a
11+# release is made.
12+#
13+# Needs the repository secret DESKTOP_SIGNING_KEY (the updater's private
14+# key; its public half is in tauri.conf.json), CLOUDFLARE_API_TOKEN and
15+# CLOUDFLARE_ACCOUNT_ID as the deploy has them, and, for macOS, a
16+# self-hosted runner on a Mac with the labels `self-hosted` and `macos`
17+# plus the repository variable DESKTOP_MACOS_RUNNER set to `true`; without
18+# it the macOS job is skipped and the release carries Windows and Linux.
19+name: Desktop release
20+
21+on:
22+ push:
23+ tags: ["desktop-v*"]
24+ workflow_dispatch:
25+ inputs:
26+ notes:
27+ description: "A line about the release, shown in the manifest"
28+ type: string
29+
30+permissions:
31+ contents: read
32+
33+concurrency:
34+ group: desktop-release
35+ cancel-in-progress: false
36+
37+env:
38+ CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
39+ WRANGLER_SEND_METRICS: "false"
40+
41+jobs:
42+ version:
43+ name: The tag names this version
44+ runs-on: ubuntu-latest
45+ outputs:
46+ version: ${{ steps.read.outputs.version }}
47+ steps:
48+ - uses: actions/checkout@v5
49+ - id: read
50+ run: |
51+ version="$(sed -n 's/^version = "\(.*\)"/\1/p' apps/desktop/src-tauri/Cargo.toml | head -1)"
52+ echo "version=$version" >> "$GITHUB_OUTPUT"
53+ if [ "${GITHUB_REF_TYPE}" = tag ] && [ "desktop-v$version" != "${GITHUB_REF_NAME}" ]; then
54+ echo "::error::The tag is ${GITHUB_REF_NAME}, but apps/desktop is $version"; exit 1
55+ fi
56+
57+ linux:
58+ name: Linux and Windows
59+ needs: version
60+ runs-on: ubuntu-latest
61+ environment: production
62+ timeout-minutes: 90
63+ steps:
64+ - uses: actions/checkout@v5
65+ - name: What Tauri needs on Linux, and to build Windows from here
66+ run: |
67+ sudo apt-get update
68+ sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev nsis lld llvm
69+ cargo install --locked cargo-xwin
70+ - uses: actions/cache@v4
71+ with:
72+ path: |
73+ ~/.cargo/registry
74+ apps/desktop/src-tauri/target
75+ key: desktop-release-linux-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }}
76+ - name: Install
77+ run: npm ci --no-audit --no-fund
78+ - name: Build and sign
79+ env:
80+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
81+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
82+ run: node scripts/desktop-release.mjs build --windows-from-linux
83+ - name: Collect
84+ run: node scripts/desktop-release.mjs collect
85+ - uses: actions/upload-artifact@v4
86+ with:
87+ name: desktop-linux
88+ path: target/desktop-release/${{ needs.version.outputs.version }}/
89+
90+ macos:
91+ name: macOS
92+ needs: version
93+ if: vars.DESKTOP_MACOS_RUNNER == 'true'
94+ runs-on: [self-hosted, macos]
95+ environment: production
96+ timeout-minutes: 90
97+ steps:
98+ - uses: actions/checkout@v5
99+ - name: Install
100+ run: npm ci --no-audit --no-fund
101+ - name: Build and sign, for Apple silicon and Intel
102+ env:
103+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.DESKTOP_SIGNING_KEY }}
104+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ""
105+ run: |
106+ rustup target add aarch64-apple-darwin x86_64-apple-darwin
107+ npx tauri build --ci --target aarch64-apple-darwin
108+ npx tauri build --ci --target x86_64-apple-darwin
109+ working-directory: apps/desktop
110+ - name: Collect
111+ run: node scripts/desktop-release.mjs collect
112+ - uses: actions/upload-artifact@v4
113+ with:
114+ name: desktop-macos
115+ path: target/desktop-release/${{ needs.version.outputs.version }}/
116+
117+ publish:
118+ name: Publish to g1t.sh/downloads/desktop
119+ needs: [version, linux, macos]
120+ # With no Mac registered the macOS job is skipped, and the release still goes out.
121+ if: always() && needs.linux.result == 'success' && (needs.macos.result == 'success' || needs.macos.result == 'skipped')
122+ runs-on: ubuntu-latest
123+ environment: production
124+ timeout-minutes: 20
125+ steps:
126+ - uses: actions/checkout@v5
127+ - uses: actions/download-artifact@v4
128+ with:
129+ pattern: desktop-*
130+ merge-multiple: true
131+ path: target/desktop-release/${{ needs.version.outputs.version }}/
132+ - name: Install Wrangler
133+ run: npm ci --workspaces=false --no-audit --no-fund
134+ - name: The manifest
135+ run: node scripts/desktop-release.mjs manifest --notes "${{ inputs.notes }}"
136+ - name: Publish
137+ env:
138+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
139+ run: node scripts/desktop-release.mjs publish
+9−0
100100 `.g1t/workflows/runner-release.yml` builds, signs and publishes it, and
101101 runners update themselves to it. `scripts/runner-release.mjs` does the
102102 same by hand.
103+- **The desktop app** is released the same way: bump `version` in
104+ `apps/desktop/src-tauri/Cargo.toml`, merge, and push a
105+ `desktop-v<version>` tag. `.g1t/workflows/desktop-release.yml` builds
106+ the installers (Windows and Linux on Linux; macOS on a Mac runner when
107+ one is registered), signs the updater's files and publishes them, and
108+ installed apps update themselves. `scripts/desktop-release.mjs` does the
109+ same by hand. The app loads the live site, so a change to the site needs
110+ no release; only a change under `apps/desktop` does. The guide is
111+ [The desktop app](https://docs.g1t.sh/guides/desktop/).
103112
104113 ## Speed
105114
+4−0
11 [workspace]
22 resolver = "3"
3+# The desktop app (Tauri) builds on its own: it needs a toolkit the services
4+# never do, and keeps `cargo test --workspace` fast. apps/desktop/src-tauri
5+# has its own Cargo.lock.
6+exclude = ["apps/desktop/src-tauri"]
37 members = ["apps/api", "crates/*", "services/actions", "services/billing", "services/events", "services/identity", "services/integrations", "services/packages", "services/webhooks", "services/repos", "services/search", "services/security", "services/work"]
48
59 [workspace.package]
+2−1
4141 | Artifacts: documents written together, live, by people and agents | Live |
4242 | Slides, designs and dashboards as artifacts | Coming |
4343 | Scratchpads: tools, reports and agents anyone can build on a copy of the data, promoted after review | Coming |
44−| Desktop app (Tauri, with a runner inside) and phone app (Expo) | Coming |
44+| Desktop app for Windows, macOS and Linux (Tauri): the site in a window of its own, notifications from the system, a badge, g1t:// links, updates that install themselves | Preview |
45+| A runner inside the desktop app, and a phone app (Expo) | Coming |
4546
4647 ### Agents
4748
+67−0
1+<!doctype html>
2+<!--
3+ The launcher: the page bundled with the desktop app, which the window
4+ opens first. It asks the app which server to use, checks that it
5+ answers, and goes there; while it works, and when it cannot, this is
6+ what you see. It is also where another g1t is chosen (?change).
7+-->
8+<html lang="en">
9+ <head>
10+ <meta charset="utf-8" />
11+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12+ <meta name="color-scheme" content="dark" />
13+ <title>g1t</title>
14+ <link rel="stylesheet" href="launcher.css" />
15+ </head>
16+ <body>
17+ <main>
18+ <svg class="mark" viewBox="0 0 256 256" shape-rendering="crispEdges" aria-hidden="true">
19+ <rect width="256" height="256" rx="56" shape-rendering="geometricPrecision" fill="#0f0f11" />
20+ <rect x="118" y="43" width="20" height="20" fill="#b6a8ff" />
21+ <rect x="93" y="68" width="20" height="20" fill="#b6a8ff" />
22+ <rect x="118" y="68" width="20" height="20" fill="#b6a8ff" />
23+ <rect x="68" y="93" width="20" height="20" fill="#b6a8ff" />
24+ <rect x="118" y="93" width="20" height="20" fill="#b6a8ff" />
25+ <rect x="118" y="118" width="20" height="20" fill="#b6a8ff" />
26+ <rect x="118" y="143" width="20" height="20" fill="#b6a8ff" />
27+ <rect x="118" y="168" width="20" height="20" fill="#b6a8ff" />
28+ <rect x="68" y="193" width="20" height="20" fill="#b6a8ff" />
29+ <rect x="93" y="193" width="20" height="20" fill="#b6a8ff" />
30+ <rect x="118" y="193" width="20" height="20" fill="#b6a8ff" />
31+ <rect x="143" y="193" width="20" height="20" fill="#b6a8ff" />
32+ <rect x="168" y="193" width="20" height="20" fill="#b6a8ff" />
33+ </svg>
34+
35+ <section id="connecting" hidden>
36+ <p class="status"><span class="pulse"></span><span id="connecting-text">Connecting to g1t…</span></p>
37+ </section>
38+
39+ <section id="offline" hidden>
40+ <h1 id="offline-title">Can't reach g1t.sh</h1>
41+ <p class="muted">Check your connection, then try again. If g1t.sh itself is down, its status page says so.</p>
42+ <div class="row">
43+ <button id="retry" type="button" class="primary">Try again</button>
44+ <button id="change" type="button">Use another g1t…</button>
45+ </div>
46+ <p class="muted small"><a id="status-link" href="https://status.g1t.sh/">status.g1t.sh</a></p>
47+ </section>
48+
49+ <section id="choose" hidden>
50+ <h1>Which g1t?</h1>
51+ <p class="muted">g1t.sh unless you run g1t yourself. Enter your g1t's address to use it instead; the app keeps it.</p>
52+ <form id="server-form">
53+ <label for="server">Address</label>
54+ <input id="server" name="server" type="url" inputmode="url" autocomplete="off" spellcheck="false" placeholder="https://g1t.example.com" />
55+ <p id="server-error" class="error" hidden></p>
56+ <div class="row">
57+ <button type="submit" class="primary">Use this g1t</button>
58+ <button id="use-default" type="button">Use g1t.sh</button>
59+ </div>
60+ </form>
61+ </section>
62+
63+ <p class="version muted small" id="version"></p>
64+ </main>
65+ <script src="launcher.js"></script>
66+ </body>
67+</html>
+158−0
1+/* The launcher, in the site's colours (packages/theme/tokens.css): a very
2+ dark gray and lavender, the mark centred, one short message. */
3+:root {
4+ --bg: #0f0f11;
5+ --fg: #ededf0;
6+ --muted: #9a9aa6;
7+ --line: #26262c;
8+ --surface: #17171b;
9+ --accent: #b6a8ff;
10+ --accent-ink: #15112e;
11+ --error: #ff8a8a;
12+ color-scheme: dark;
13+}
14+* {
15+ box-sizing: border-box;
16+}
17+html,
18+body {
19+ height: 100%;
20+ margin: 0;
21+}
22+body {
23+ background: var(--bg);
24+ color: var(--fg);
25+ font: 15px/1.5 system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
26+ -webkit-font-smoothing: antialiased;
27+ display: grid;
28+ place-items: center;
29+ user-select: none;
30+ cursor: default;
31+}
32+main {
33+ width: min(26rem, calc(100vw - 2rem));
34+ text-align: center;
35+}
36+.mark {
37+ width: 72px;
38+ height: 72px;
39+ display: block;
40+ margin: 0 auto 1.5rem;
41+ border-radius: 16px;
42+ box-shadow: 0 0 0 1px var(--line);
43+}
44+h1 {
45+ font-size: 1.125rem;
46+ font-weight: 600;
47+ margin: 0 0 0.5rem;
48+}
49+p {
50+ margin: 0 0 1rem;
51+}
52+.muted {
53+ color: var(--muted);
54+}
55+.small {
56+ font-size: 0.8125rem;
57+}
58+.error {
59+ color: var(--error);
60+ font-size: 0.875rem;
61+ text-align: left;
62+}
63+.status {
64+ display: inline-flex;
65+ align-items: center;
66+ gap: 0.625rem;
67+ color: var(--muted);
68+}
69+.pulse {
70+ width: 8px;
71+ height: 8px;
72+ border-radius: 50%;
73+ background: var(--accent);
74+ animation: pulse 1.4s ease-in-out infinite;
75+}
76+@keyframes pulse {
77+ 0%,
78+ 100% {
79+ opacity: 0.35;
80+ transform: scale(0.85);
81+ }
82+ 50% {
83+ opacity: 1;
84+ transform: scale(1);
85+ }
86+}
87+@media (prefers-reduced-motion: reduce) {
88+ .pulse {
89+ animation: none;
90+ }
91+}
92+.row {
93+ display: flex;
94+ gap: 0.5rem;
95+ justify-content: center;
96+ flex-wrap: wrap;
97+ margin-bottom: 1rem;
98+}
99+button {
100+ font: inherit;
101+ font-size: 0.875rem;
102+ font-weight: 500;
103+ padding: 0.5rem 0.875rem;
104+ border-radius: 8px;
105+ border: 1px solid var(--line);
106+ background: var(--surface);
107+ color: var(--fg);
108+ cursor: default;
109+}
110+button:hover {
111+ border-color: #3a3a44;
112+}
113+button:focus-visible,
114+input:focus-visible {
115+ outline: 2px solid var(--accent);
116+ outline-offset: 2px;
117+}
118+button.primary {
119+ background: var(--accent);
120+ border-color: var(--accent);
121+ color: var(--accent-ink);
122+}
123+button.primary:hover {
124+ filter: brightness(1.06);
125+}
126+form {
127+ text-align: left;
128+}
129+label {
130+ display: block;
131+ font-size: 0.8125rem;
132+ color: var(--muted);
133+ margin-bottom: 0.25rem;
134+}
135+input {
136+ font: inherit;
137+ width: 100%;
138+ padding: 0.5rem 0.625rem;
139+ border-radius: 8px;
140+ border: 1px solid var(--line);
141+ background: var(--surface);
142+ color: var(--fg);
143+ margin-bottom: 0.75rem;
144+ user-select: text;
145+}
146+a {
147+ color: var(--muted);
148+ text-decoration: underline;
149+ text-underline-offset: 3px;
150+}
151+a:hover {
152+ color: var(--fg);
153+}
154+.version {
155+ margin-top: 2.5rem;
156+ margin-bottom: 0;
157+ font-variant-numeric: tabular-nums;
158+}
+106−0
1+// The launcher's work: ask the app for its server, check that it answers,
2+// go there. `?change` opens the choice of server instead. Everything it
3+// asks is a command in src/bridge.rs, answered only to this page.
4+(function () {
5+ var sections = { connecting: byId("connecting"), offline: byId("offline"), choose: byId("choose") };
6+ var info = null;
7+
8+ function byId(id) {
9+ return document.getElementById(id);
10+ }
11+
12+ function invoke(command, args) {
13+ var core = window.__TAURI__ && window.__TAURI__.core;
14+ if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
15+ return Promise.reject(new Error("the launcher cannot reach the app"));
16+ }
17+
18+ function show(name) {
19+ Object.keys(sections).forEach(function (key) {
20+ sections[key].hidden = key !== name;
21+ });
22+ }
23+
24+ function hostOf(server) {
25+ try {
26+ return new URL(server).host;
27+ } catch (error) {
28+ return server;
29+ }
30+ }
31+
32+ function connect(server) {
33+ show("connecting");
34+ byId("connecting-text").textContent = "Connecting to " + hostOf(server) + "…";
35+ invoke("reachable", { url: server })
36+ .then(function (ok) {
37+ if (ok) location.replace(server);
38+ else offline(server);
39+ })
40+ .catch(function () {
41+ offline(server);
42+ });
43+ }
44+
45+ function offline(server) {
46+ byId("offline-title").textContent = "Can't reach " + hostOf(server);
47+ var status = byId("status-link");
48+ var isDefault = hostOf(server) === "g1t.sh";
49+ status.parentElement.hidden = !isDefault;
50+ show("offline");
51+ byId("retry").onclick = function () {
52+ connect(server);
53+ };
54+ }
55+
56+ function choose(server) {
57+ show("choose");
58+ var input = byId("server");
59+ input.value = hostOf(server) === "g1t.sh" ? "" : server;
60+ byId("server-error").hidden = true;
61+ input.focus();
62+ }
63+
64+ function fail(message) {
65+ var error = byId("server-error");
66+ error.textContent = message;
67+ error.hidden = false;
68+ }
69+
70+ byId("change").onclick = function () {
71+ choose(info ? info.server : "https://g1t.sh/");
72+ };
73+ byId("use-default").onclick = function () {
74+ invoke("set_server", { url: "https://g1t.sh" }).then(connect, fail);
75+ };
76+ byId("server-form").onsubmit = function (event) {
77+ event.preventDefault();
78+ var typed = byId("server").value.trim();
79+ if (!typed) return fail("Enter your g1t's address, or use g1t.sh.");
80+ invoke("set_server", { url: typed }).then(connect, function (error) {
81+ fail(typeof error === "string" ? error : "That address could not be kept.");
82+ });
83+ };
84+ // Links out, in the system's browser (src/bridge.js handles the rest).
85+ document.addEventListener("click", function (event) {
86+ var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
87+ if (!anchor) return;
88+ event.preventDefault();
89+ invoke("open_url", { url: anchor.href }).catch(function () {});
90+ });
91+
92+ invoke("desktop_info")
93+ .then(function (found) {
94+ info = found;
95+ byId("version").textContent = "g1t " + found.version;
96+ if (new URLSearchParams(location.search).has("change")) choose(found.server);
97+ else connect(found.server);
98+ })
99+ .catch(function () {
100+ // No app behind the page: a browser opened the file. Nothing to do.
101+ show("offline");
102+ byId("offline-title").textContent = "This page belongs inside the g1t desktop app";
103+ byId("retry").hidden = true;
104+ byId("change").hidden = true;
105+ });
106+})();
+14−0
1+{
2+ "name": "@g1t/desktop",
3+ "private": true,
4+ "type": "module",
5+ "description": "The g1t desktop app: the site in a window of its own (Tauri), with native notifications, a badge, a tray, g1t:// links and updates.",
6+ "scripts": {
7+ "dev": "tauri dev",
8+ "build": "tauri build",
9+ "tauri": "tauri"
10+ },
11+ "devDependencies": {
12+ "@tauri-apps/cli": "^2.12.2"
13+ }
14+}
+3−0
1+# Built by cargo and the Tauri CLI.
2+/target/
3+/gen/schemas/
+5779−0
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+version = 4
4+
5+[[package]]
6+name = "adler2"
7+version = "2.0.1"
8+source = "registry+https://github.com/rust-lang/crates.io-index"
9+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10+
11+[[package]]
12+name = "aho-corasick"
13+version = "1.1.5"
14+source = "registry+https://github.com/rust-lang/crates.io-index"
15+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
16+dependencies = [
17+ "memchr",
18+]
19+
20+[[package]]
21+name = "alloc-no-stdlib"
22+version = "3.0.0"
23+source = "registry+https://github.com/rust-lang/crates.io-index"
24+checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
25+
26+[[package]]
27+name = "alloc-stdlib"
28+version = "0.3.0"
29+source = "registry+https://github.com/rust-lang/crates.io-index"
30+checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
31+dependencies = [
32+ "alloc-no-stdlib",
33+]
34+
35+[[package]]
36+name = "android_system_properties"
37+version = "0.1.6"
38+source = "registry+https://github.com/rust-lang/crates.io-index"
39+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
40+dependencies = [
41+ "libc",
42+]
43+
44+[[package]]
45+name = "anyhow"
46+version = "1.0.104"
47+source = "registry+https://github.com/rust-lang/crates.io-index"
48+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
49+
50+[[package]]
51+name = "arbitrary"
52+version = "1.5.0"
53+source = "registry+https://github.com/rust-lang/crates.io-index"
54+checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
55+dependencies = [
56+ "derive_arbitrary",
57+]
58+
59+[[package]]
60+name = "async-broadcast"
61+version = "0.7.2"
62+source = "registry+https://github.com/rust-lang/crates.io-index"
63+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
64+dependencies = [
65+ "event-listener",
66+ "event-listener-strategy",
67+ "futures-core",
68+ "pin-project-lite",
69+]
70+
71+[[package]]
72+name = "async-channel"
73+version = "2.5.0"
74+source = "registry+https://github.com/rust-lang/crates.io-index"
75+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
76+dependencies = [
77+ "concurrent-queue",
78+ "event-listener-strategy",
79+ "futures-core",
80+ "pin-project-lite",
81+]
82+
83+[[package]]
84+name = "async-executor"
85+version = "1.14.0"
86+source = "registry+https://github.com/rust-lang/crates.io-index"
87+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
88+dependencies = [
89+ "async-task",
90+ "concurrent-queue",
91+ "fastrand",
92+ "futures-lite",
93+ "pin-project-lite",
94+ "slab",
95+]
96+
97+[[package]]
98+name = "async-io"
99+version = "2.6.0"
100+source = "registry+https://github.com/rust-lang/crates.io-index"
101+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
102+dependencies = [
103+ "autocfg",
104+ "cfg-if",
105+ "concurrent-queue",
106+ "futures-io",
107+ "futures-lite",
108+ "parking",
109+ "polling",
110+ "rustix",
111+ "slab",
112+ "windows-sys 0.61.2",
113+]
114+
115+[[package]]
116+name = "async-lock"
117+version = "3.4.2"
118+source = "registry+https://github.com/rust-lang/crates.io-index"
119+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
120+dependencies = [
121+ "event-listener",
122+ "event-listener-strategy",
123+ "pin-project-lite",
124+]
125+
126+[[package]]
127+name = "async-process"
128+version = "2.5.0"
129+source = "registry+https://github.com/rust-lang/crates.io-index"
130+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
131+dependencies = [
132+ "async-channel",
133+ "async-io",
134+ "async-lock",
135+ "async-signal",
136+ "async-task",
137+ "blocking",
138+ "cfg-if",
139+ "event-listener",
140+ "futures-lite",
141+ "rustix",
142+]
143+
144+[[package]]
145+name = "async-recursion"
146+version = "1.2.0"
147+source = "registry+https://github.com/rust-lang/crates.io-index"
148+checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"
149+dependencies = [
150+ "proc-macro2",
151+ "quote",
152+ "syn 3.0.7",
153+]
154+
155+[[package]]
156+name = "async-signal"
157+version = "0.2.14"
158+source = "registry+https://github.com/rust-lang/crates.io-index"
159+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
160+dependencies = [
161+ "async-io",
162+ "async-lock",
163+ "atomic-waker",
164+ "cfg-if",
165+ "futures-core",
166+ "futures-io",
167+ "rustix",
168+ "signal-hook-registry",
169+ "slab",
170+ "windows-sys 0.61.2",
171+]
172+
173+[[package]]
174+name = "async-task"
175+version = "4.7.1"
176+source = "registry+https://github.com/rust-lang/crates.io-index"
177+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
178+
179+[[package]]
180+name = "async-trait"
181+version = "0.1.92"
182+source = "registry+https://github.com/rust-lang/crates.io-index"
183+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
184+dependencies = [
185+ "proc-macro2",
186+ "quote",
187+ "syn 3.0.7",
188+]
189+
190+[[package]]
191+name = "atk"
192+version = "0.18.2"
193+source = "registry+https://github.com/rust-lang/crates.io-index"
194+checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
195+dependencies = [
196+ "atk-sys",
197+ "glib",
198+ "libc",
199+]
200+
201+[[package]]
202+name = "atk-sys"
203+version = "0.18.2"
204+source = "registry+https://github.com/rust-lang/crates.io-index"
205+checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
206+dependencies = [
207+ "glib-sys",
208+ "gobject-sys",
209+ "libc",
210+ "system-deps",
211+]
212+
213+[[package]]
214+name = "atomic-waker"
215+version = "1.1.2"
216+source = "registry+https://github.com/rust-lang/crates.io-index"
217+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
218+
219+[[package]]
220+name = "autocfg"
221+version = "1.5.1"
222+source = "registry+https://github.com/rust-lang/crates.io-index"
223+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
224+
225+[[package]]
226+name = "base64"
227+version = "0.21.7"
228+source = "registry+https://github.com/rust-lang/crates.io-index"
229+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
230+
231+[[package]]
232+name = "base64"
233+version = "0.22.1"
234+source = "registry+https://github.com/rust-lang/crates.io-index"
235+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
236+
237+[[package]]
238+name = "base64"
239+version = "0.23.1"
240+source = "registry+https://github.com/rust-lang/crates.io-index"
241+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
242+
243+[[package]]
244+name = "bit-set"
245+version = "0.8.0"
246+source = "registry+https://github.com/rust-lang/crates.io-index"
247+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
248+dependencies = [
249+ "bit-vec",
250+]
251+
252+[[package]]
253+name = "bit-vec"
254+version = "0.8.0"
255+source = "registry+https://github.com/rust-lang/crates.io-index"
256+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
257+
258+[[package]]
259+name = "bitflags"
260+version = "1.3.2"
261+source = "registry+https://github.com/rust-lang/crates.io-index"
262+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
263+
264+[[package]]
265+name = "bitflags"
266+version = "2.13.2"
267+source = "registry+https://github.com/rust-lang/crates.io-index"
268+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
269+dependencies = [
270+ "serde_core",
271+]
272+
273+[[package]]
274+name = "block-buffer"
275+version = "0.10.4"
276+source = "registry+https://github.com/rust-lang/crates.io-index"
277+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
278+dependencies = [
279+ "generic-array",
280+]
281+
282+[[package]]
283+name = "block2"
284+version = "0.6.2"
285+source = "registry+https://github.com/rust-lang/crates.io-index"
286+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
287+dependencies = [
288+ "objc2",
289+]
290+
291+[[package]]
292+name = "blocking"
293+version = "1.7.0"
294+source = "registry+https://github.com/rust-lang/crates.io-index"
295+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
296+dependencies = [
297+ "async-channel",
298+ "async-task",
299+ "futures-io",
300+ "futures-lite",
301+ "piper",
302+]
303+
304+[[package]]
305+name = "brotli"
306+version = "9.0.0"
307+source = "registry+https://github.com/rust-lang/crates.io-index"
308+checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
309+dependencies = [
310+ "alloc-no-stdlib",
311+ "alloc-stdlib",
312+ "brotli-decompressor",
313+]
314+
315+[[package]]
316+name = "brotli-decompressor"
317+version = "6.0.1"
318+source = "registry+https://github.com/rust-lang/crates.io-index"
319+checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
320+dependencies = [
321+ "alloc-no-stdlib",
322+ "alloc-stdlib",
323+]
324+
325+[[package]]
326+name = "bs58"
327+version = "0.5.1"
328+source = "registry+https://github.com/rust-lang/crates.io-index"
329+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
330+dependencies = [
331+ "tinyvec",
332+]
333+
334+[[package]]
335+name = "bumpalo"
336+version = "3.20.3"
337+source = "registry+https://github.com/rust-lang/crates.io-index"
338+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
339+
340+[[package]]
341+name = "bytemuck"
342+version = "1.25.2"
343+source = "registry+https://github.com/rust-lang/crates.io-index"
344+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
345+
346+[[package]]
347+name = "byteorder"
348+version = "1.5.0"
349+source = "registry+https://github.com/rust-lang/crates.io-index"
350+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
351+
352+[[package]]
353+name = "byteorder-lite"
354+version = "0.1.0"
355+source = "registry+https://github.com/rust-lang/crates.io-index"
356+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
357+
358+[[package]]
359+name = "bytes"
360+version = "1.12.1"
361+source = "registry+https://github.com/rust-lang/crates.io-index"
362+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
363+dependencies = [
364+ "serde",
365+]
366+
367+[[package]]
368+name = "cairo-rs"
369+version = "0.18.5"
370+source = "registry+https://github.com/rust-lang/crates.io-index"
371+checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
372+dependencies = [
373+ "bitflags 2.13.2",
374+ "cairo-sys-rs",
375+ "glib",
376+ "libc",
377+ "once_cell",
378+ "thiserror 1.0.69",
379+]
380+
381+[[package]]
382+name = "cairo-sys-rs"
383+version = "0.18.2"
384+source = "registry+https://github.com/rust-lang/crates.io-index"
385+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
386+dependencies = [
387+ "glib-sys",
388+ "libc",
389+ "system-deps",
390+]
391+
392+[[package]]
393+name = "camino"
394+version = "1.2.6"
395+source = "registry+https://github.com/rust-lang/crates.io-index"
396+checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591"
397+dependencies = [
398+ "serde_core",
399+]
400+
401+[[package]]
402+name = "cargo-platform"
403+version = "0.1.9"
404+source = "registry+https://github.com/rust-lang/crates.io-index"
405+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
406+dependencies = [
407+ "serde",
408+]
409+
410+[[package]]
411+name = "cargo_metadata"
412+version = "0.19.2"
413+source = "registry+https://github.com/rust-lang/crates.io-index"
414+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
415+dependencies = [
416+ "camino",
417+ "cargo-platform",
418+ "semver",
419+ "serde",
420+ "serde_json",
421+ "thiserror 2.0.21",
422+]
423+
424+[[package]]
425+name = "cargo_toml"
426+version = "1.0.1"
427+source = "registry+https://github.com/rust-lang/crates.io-index"
428+checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9"
429+dependencies = [
430+ "semver",
431+ "serde",
432+ "toml 1.1.8+spec-1.1.0",
433+]
434+
435+[[package]]
436+name = "cc"
437+version = "1.7.0"
438+source = "registry+https://github.com/rust-lang/crates.io-index"
439+checksum = "50a649af8a827553c29fb0cb4bd4a6f1a0dd695bd3232b9bc98bd9c8a3ffbb8b"
440+dependencies = [
441+ "find-msvc-tools",
442+ "shlex",
443+]
444+
445+[[package]]
446+name = "cesu8"
447+version = "1.1.0"
448+source = "registry+https://github.com/rust-lang/crates.io-index"
449+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
450+
451+[[package]]
452+name = "cfb"
453+version = "0.14.0"
454+source = "registry+https://github.com/rust-lang/crates.io-index"
455+checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3"
456+dependencies = [
457+ "fnv",
458+ "uuid",
459+ "web-time",
460+]
461+
462+[[package]]
463+name = "cfg-expr"
464+version = "0.15.8"
465+source = "registry+https://github.com/rust-lang/crates.io-index"
466+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
467+dependencies = [
468+ "smallvec",
469+ "target-lexicon",
470+]
471+
472+[[package]]
473+name = "cfg-if"
474+version = "1.0.5"
475+source = "registry+https://github.com/rust-lang/crates.io-index"
476+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
477+
478+[[package]]
479+name = "cfg_aliases"
480+version = "0.2.2"
481+source = "registry+https://github.com/rust-lang/crates.io-index"
482+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
483+
484+[[package]]
485+name = "chacha20"
486+version = "0.10.2"
487+source = "registry+https://github.com/rust-lang/crates.io-index"
488+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
489+dependencies = [
490+ "cfg-if",
491+ "cpufeatures 0.3.1",
492+ "rand_core 0.10.1",
493+]
494+
495+[[package]]
496+name = "chrono"
497+version = "0.4.45"
498+source = "registry+https://github.com/rust-lang/crates.io-index"
499+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
500+dependencies = [
501+ "iana-time-zone",
502+ "num-traits",
503+ "serde",
504+ "windows-link",
505+]
506+
507+[[package]]
508+name = "combine"
509+version = "4.6.8"
510+source = "registry+https://github.com/rust-lang/crates.io-index"
511+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
512+dependencies = [
513+ "bytes",
514+ "memchr",
515+]
516+
517+[[package]]
518+name = "concurrent-queue"
519+version = "2.5.0"
520+source = "registry+https://github.com/rust-lang/crates.io-index"
521+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
522+dependencies = [
523+ "crossbeam-utils",
524+]
525+
526+[[package]]
527+name = "const-random"
528+version = "0.1.18"
529+source = "registry+https://github.com/rust-lang/crates.io-index"
530+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
531+dependencies = [
532+ "const-random-macro",
533+]
534+
535+[[package]]
536+name = "const-random-macro"
537+version = "0.1.16"
538+source = "registry+https://github.com/rust-lang/crates.io-index"
539+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
540+dependencies = [
541+ "getrandom 0.2.17",
542+ "once_cell",
543+ "tiny-keccak",
544+]
545+
546+[[package]]
547+name = "cookie"
548+version = "0.18.2"
549+source = "registry+https://github.com/rust-lang/crates.io-index"
550+checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
551+dependencies = [
552+ "time",
553+ "version_check",
554+]
555+
556+[[package]]
557+name = "core-foundation"
558+version = "0.9.4"
559+source = "registry+https://github.com/rust-lang/crates.io-index"
560+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
561+dependencies = [
562+ "core-foundation-sys",
563+ "libc",
564+]
565+
566+[[package]]
567+name = "core-foundation"
568+version = "0.10.1"
569+source = "registry+https://github.com/rust-lang/crates.io-index"
570+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
571+dependencies = [
572+ "core-foundation-sys",
573+ "libc",
574+]
575+
576+[[package]]
577+name = "core-foundation-sys"
578+version = "0.8.7"
579+source = "registry+https://github.com/rust-lang/crates.io-index"
580+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
581+
582+[[package]]
583+name = "core-graphics"
584+version = "0.25.0"
585+source = "registry+https://github.com/rust-lang/crates.io-index"
586+checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
587+dependencies = [
588+ "bitflags 2.13.2",
589+ "core-foundation 0.10.1",
590+ "core-graphics-types",
591+ "foreign-types",
592+ "libc",
593+]
594+
595+[[package]]
596+name = "core-graphics-types"
597+version = "0.2.0"
598+source = "registry+https://github.com/rust-lang/crates.io-index"
599+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
600+dependencies = [
601+ "bitflags 2.13.2",
602+ "core-foundation 0.10.1",
603+ "libc",
604+]
605+
606+[[package]]
607+name = "cpufeatures"
608+version = "0.2.17"
609+source = "registry+https://github.com/rust-lang/crates.io-index"
610+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
611+dependencies = [
612+ "libc",
613+]
614+
615+[[package]]
616+name = "cpufeatures"
617+version = "0.3.1"
618+source = "registry+https://github.com/rust-lang/crates.io-index"
619+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
620+dependencies = [
621+ "libc",
622+]
623+
624+[[package]]
625+name = "crc32fast"
626+version = "1.5.2"
627+source = "registry+https://github.com/rust-lang/crates.io-index"
628+checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
629+dependencies = [
630+ "cfg-if",
631+]
632+
633+[[package]]
634+name = "crossbeam-channel"
635+version = "0.5.17"
636+source = "registry+https://github.com/rust-lang/crates.io-index"
637+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
638+dependencies = [
639+ "crossbeam-utils",
640+]
641+
642+[[package]]
643+name = "crossbeam-utils"
644+version = "0.8.23"
645+source = "registry+https://github.com/rust-lang/crates.io-index"
646+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
647+
648+[[package]]
649+name = "crunchy"
650+version = "0.2.4"
651+source = "registry+https://github.com/rust-lang/crates.io-index"
652+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
653+
654+[[package]]
655+name = "crypto-common"
656+version = "0.1.7"
657+source = "registry+https://github.com/rust-lang/crates.io-index"
658+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
659+dependencies = [
660+ "generic-array",
661+ "typenum",
662+]
663+
664+[[package]]
665+name = "cssparser"
666+version = "0.37.0"
667+source = "registry+https://github.com/rust-lang/crates.io-index"
668+checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98"
669+dependencies = [
670+ "cssparser-macros",
671+ "dtoa-short",
672+ "itoa",
673+ "phf",
674+ "smallvec",
675+]
676+
677+[[package]]
678+name = "cssparser-macros"
679+version = "0.7.1"
680+source = "registry+https://github.com/rust-lang/crates.io-index"
681+checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a"
682+dependencies = [
683+ "quote",
684+ "syn 3.0.7",
685+]
686+
687+[[package]]
688+name = "ctor"
689+version = "1.0.13"
690+source = "registry+https://github.com/rust-lang/crates.io-index"
691+checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
692+
693+[[package]]
694+name = "darling"
695+version = "0.24.1"
696+source = "registry+https://github.com/rust-lang/crates.io-index"
697+checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
698+dependencies = [
699+ "darling_core",
700+ "darling_macro",
701+]
702+
703+[[package]]
704+name = "darling_core"
705+version = "0.24.1"
706+source = "registry+https://github.com/rust-lang/crates.io-index"
707+checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
708+dependencies = [
709+ "ident_case",
710+ "proc-macro2",
711+ "quote",
712+ "strsim",
713+ "syn 3.0.7",
714+]
715+
716+[[package]]
717+name = "darling_macro"
718+version = "0.24.1"
719+source = "registry+https://github.com/rust-lang/crates.io-index"
720+checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
721+dependencies = [
722+ "darling_core",
723+ "quote",
724+ "syn 3.0.7",
725+]
726+
727+[[package]]
728+name = "dbus"
729+version = "0.9.12"
730+source = "registry+https://github.com/rust-lang/crates.io-index"
731+checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
732+dependencies = [
733+ "libc",
734+ "libdbus-sys",
735+ "windows-sys 0.61.2",
736+]
737+
738+[[package]]
739+name = "defmt"
740+version = "1.1.1"
741+source = "registry+https://github.com/rust-lang/crates.io-index"
742+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
743+dependencies = [
744+ "bitflags 1.3.2",
745+ "defmt-macros",
746+]
747+
748+[[package]]
749+name = "defmt-macros"
750+version = "1.1.1"
751+source = "registry+https://github.com/rust-lang/crates.io-index"
752+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
753+dependencies = [
754+ "defmt-parser",
755+ "proc-macro2",
756+ "quote",
757+ "syn 2.0.119",
758+]
759+
760+[[package]]
761+name = "defmt-parser"
762+version = "1.0.0"
763+source = "registry+https://github.com/rust-lang/crates.io-index"
764+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
765+dependencies = [
766+ "thiserror 2.0.21",
767+]
768+
769+[[package]]
770+name = "deranged"
771+version = "0.5.8"
772+source = "registry+https://github.com/rust-lang/crates.io-index"
773+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
774+dependencies = [
775+ "serde_core",
776+]
777+
778+[[package]]
779+name = "derive_arbitrary"
780+version = "1.5.0"
781+source = "registry+https://github.com/rust-lang/crates.io-index"
782+checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac"
783+dependencies = [
784+ "proc-macro2",
785+ "quote",
786+ "syn 3.0.7",
787+]
788+
789+[[package]]
790+name = "derive_more"
791+version = "2.1.1"
792+source = "registry+https://github.com/rust-lang/crates.io-index"
793+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
794+dependencies = [
795+ "derive_more-impl",
796+]
797+
798+[[package]]
799+name = "derive_more-impl"
800+version = "2.1.1"
801+source = "registry+https://github.com/rust-lang/crates.io-index"
802+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
803+dependencies = [
804+ "proc-macro2",
805+ "quote",
806+ "rustc_version",
807+ "syn 2.0.119",
808+]
809+
810+[[package]]
811+name = "digest"
812+version = "0.10.7"
813+source = "registry+https://github.com/rust-lang/crates.io-index"
814+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
815+dependencies = [
816+ "block-buffer",
817+ "crypto-common",
818+]
819+
820+[[package]]
821+name = "dirs"
822+version = "7.0.0"
823+source = "registry+https://github.com/rust-lang/crates.io-index"
824+checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f"
825+dependencies = [
826+ "dirs-sys",
827+]
828+
829+[[package]]
830+name = "dirs-sys"
831+version = "0.5.0"
832+source = "registry+https://github.com/rust-lang/crates.io-index"
833+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
834+dependencies = [
835+ "libc",
836+ "option-ext",
837+ "redox_users",
838+ "windows-sys 0.61.2",
839+]
840+
841+[[package]]
842+name = "dispatch2"
843+version = "0.3.1"
844+source = "registry+https://github.com/rust-lang/crates.io-index"
845+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
846+dependencies = [
847+ "bitflags 2.13.2",
848+ "block2",
849+ "libc",
850+ "objc2",
851+]
852+
853+[[package]]
854+name = "displaydoc"
855+version = "0.2.7"
856+source = "registry+https://github.com/rust-lang/crates.io-index"
857+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
858+dependencies = [
859+ "proc-macro2",
860+ "quote",
861+ "syn 3.0.7",
862+]
863+
864+[[package]]
865+name = "dlopen2"
866+version = "0.8.2"
867+source = "registry+https://github.com/rust-lang/crates.io-index"
868+checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
869+dependencies = [
870+ "dlopen2_derive",
871+ "libc",
872+ "once_cell",
873+ "winapi",
874+]
875+
876+[[package]]
877+name = "dlopen2_derive"
878+version = "0.4.3"
879+source = "registry+https://github.com/rust-lang/crates.io-index"
880+checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
881+dependencies = [
882+ "proc-macro2",
883+ "quote",
884+ "syn 2.0.119",
885+]
886+
887+[[package]]
888+name = "dlv-list"
889+version = "0.5.2"
890+source = "registry+https://github.com/rust-lang/crates.io-index"
891+checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
892+dependencies = [
893+ "const-random",
894+]
895+
896+[[package]]
897+name = "dom_query"
898+version = "0.28.0"
899+source = "registry+https://github.com/rust-lang/crates.io-index"
900+checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc"
901+dependencies = [
902+ "bit-set",
903+ "cssparser",
904+ "foldhash",
905+ "html5ever",
906+ "precomputed-hash",
907+ "selectors",
908+ "tendril",
909+]
910+
911+[[package]]
912+name = "dpi"
913+version = "0.1.2"
914+source = "registry+https://github.com/rust-lang/crates.io-index"
915+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
916+dependencies = [
917+ "serde",
918+]
919+
920+[[package]]
921+name = "dtoa"
922+version = "1.0.11"
923+source = "registry+https://github.com/rust-lang/crates.io-index"
924+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
925+
926+[[package]]
927+name = "dtoa-short"
928+version = "0.3.5"
929+source = "registry+https://github.com/rust-lang/crates.io-index"
930+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
931+dependencies = [
932+ "dtoa",
933+]
934+
935+[[package]]
936+name = "dunce"
937+version = "1.0.5"
938+source = "registry+https://github.com/rust-lang/crates.io-index"
939+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
940+
941+[[package]]
942+name = "dyn-clone"
943+version = "1.0.20"
944+source = "registry+https://github.com/rust-lang/crates.io-index"
945+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
946+
947+[[package]]
948+name = "embed-resource"
949+version = "3.0.12"
950+source = "registry+https://github.com/rust-lang/crates.io-index"
951+checksum = "6168487b5e7eb2673e6efe1200b1848016e436a6ca0b8031bf04bd9d493a249f"
952+dependencies = [
953+ "cc",
954+ "memchr",
955+ "rustc_version",
956+ "toml 1.1.8+spec-1.1.0",
957+ "vswhom",
958+ "winreg",
959+]
960+
961+[[package]]
962+name = "embed_plist"
963+version = "1.2.2"
964+source = "registry+https://github.com/rust-lang/crates.io-index"
965+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
966+
967+[[package]]
968+name = "endi"
969+version = "1.1.1"
970+source = "registry+https://github.com/rust-lang/crates.io-index"
971+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
972+
973+[[package]]
974+name = "enumflags2"
975+version = "0.7.12"
976+source = "registry+https://github.com/rust-lang/crates.io-index"
977+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
978+dependencies = [
979+ "enumflags2_derive",
980+ "serde",
981+]
982+
983+[[package]]
984+name = "enumflags2_derive"
985+version = "0.7.12"
986+source = "registry+https://github.com/rust-lang/crates.io-index"
987+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
988+dependencies = [
989+ "proc-macro2",
990+ "quote",
991+ "syn 2.0.119",
992+]
993+
994+[[package]]
995+name = "equivalent"
996+version = "1.0.2"
997+source = "registry+https://github.com/rust-lang/crates.io-index"
998+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
999+
1000+[[package]]
1001+name = "erased-serde"
1002+version = "0.4.10"
1003+source = "registry+https://github.com/rust-lang/crates.io-index"
1004+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
1005+dependencies = [
1006+ "serde",
1007+ "serde_core",
1008+ "typeid",
1009+]
1010+
1011+[[package]]
1012+name = "errno"
1013+version = "0.3.14"
1014+source = "registry+https://github.com/rust-lang/crates.io-index"
1015+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1016+dependencies = [
1017+ "libc",
1018+ "windows-sys 0.61.2",
1019+]
1020+
1021+[[package]]
1022+name = "event-listener"
1023+version = "5.4.2"
1024+source = "registry+https://github.com/rust-lang/crates.io-index"
1025+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
1026+dependencies = [
1027+ "parking",
1028+ "pin-project-lite",
1029+]
1030+
1031+[[package]]
1032+name = "event-listener-strategy"
1033+version = "0.5.4"
1034+source = "registry+https://github.com/rust-lang/crates.io-index"
1035+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
1036+dependencies = [
1037+ "event-listener",
1038+ "pin-project-lite",
1039+]
1040+
1041+[[package]]
1042+name = "fastrand"
1043+version = "2.5.0"
1044+source = "registry+https://github.com/rust-lang/crates.io-index"
1045+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
1046+
1047+[[package]]
1048+name = "fdeflate"
1049+version = "0.3.7"
1050+source = "registry+https://github.com/rust-lang/crates.io-index"
1051+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
1052+dependencies = [
1053+ "simd-adler32",
1054+]
1055+
1056+[[package]]
1057+name = "field-offset"
1058+version = "0.3.6"
1059+source = "registry+https://github.com/rust-lang/crates.io-index"
1060+checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
1061+dependencies = [
1062+ "memoffset",
1063+ "rustc_version",
1064+]
1065+
1066+[[package]]
1067+name = "filetime"
1068+version = "0.2.29"
1069+source = "registry+https://github.com/rust-lang/crates.io-index"
1070+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
1071+dependencies = [
1072+ "cfg-if",
1073+ "libc",
1074+]
1075+
1076+[[package]]
1077+name = "find-msvc-tools"
1078+version = "0.1.14"
1079+source = "registry+https://github.com/rust-lang/crates.io-index"
1080+checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
1081+
1082+[[package]]
1083+name = "flate2"
1084+version = "1.1.10"
1085+source = "registry+https://github.com/rust-lang/crates.io-index"
1086+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
1087+dependencies = [
1088+ "crc32fast",
1089+ "miniz_oxide 0.9.1",
1090+ "zlib-rs",
1091+]
1092+
1093+[[package]]
1094+name = "fnv"
1095+version = "1.0.7"
1096+source = "registry+https://github.com/rust-lang/crates.io-index"
1097+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1098+
1099+[[package]]
1100+name = "foldhash"
1101+version = "0.2.0"
1102+source = "registry+https://github.com/rust-lang/crates.io-index"
1103+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
1104+
1105+[[package]]
1106+name = "foreign-types"
1107+version = "0.5.0"
1108+source = "registry+https://github.com/rust-lang/crates.io-index"
1109+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
1110+dependencies = [
1111+ "foreign-types-macros",
1112+ "foreign-types-shared",
1113+]
1114+
1115+[[package]]
1116+name = "foreign-types-macros"
1117+version = "0.2.4"
1118+source = "registry+https://github.com/rust-lang/crates.io-index"
1119+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
1120+dependencies = [
1121+ "proc-macro2",
1122+ "quote",
1123+ "syn 3.0.7",
1124+]
1125+
1126+[[package]]
1127+name = "foreign-types-shared"
1128+version = "0.3.1"
1129+source = "registry+https://github.com/rust-lang/crates.io-index"
1130+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
1131+
1132+[[package]]
1133+name = "form_urlencoded"
1134+version = "1.2.2"
1135+source = "registry+https://github.com/rust-lang/crates.io-index"
1136+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
1137+dependencies = [
1138+ "percent-encoding",
1139+]
1140+
1141+[[package]]
1142+name = "futures-channel"
1143+version = "0.3.34"
1144+source = "registry+https://github.com/rust-lang/crates.io-index"
1145+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
1146+dependencies = [
1147+ "futures-core",
1148+]
1149+
1150+[[package]]
1151+name = "futures-core"
1152+version = "0.3.34"
1153+source = "registry+https://github.com/rust-lang/crates.io-index"
1154+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
1155+
1156+[[package]]
1157+name = "futures-executor"
1158+version = "0.3.34"
1159+source = "registry+https://github.com/rust-lang/crates.io-index"
1160+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
1161+dependencies = [
1162+ "futures-core",
1163+ "futures-task",
1164+ "futures-util",
1165+]
1166+
1167+[[package]]
1168+name = "futures-io"
1169+version = "0.3.34"
1170+source = "registry+https://github.com/rust-lang/crates.io-index"
1171+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
1172+
1173+[[package]]
1174+name = "futures-lite"
1175+version = "2.6.1"
1176+source = "registry+https://github.com/rust-lang/crates.io-index"
1177+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
1178+dependencies = [
1179+ "fastrand",
1180+ "futures-core",
1181+ "futures-io",
1182+ "parking",
1183+ "pin-project-lite",
1184+]
1185+
1186+[[package]]
1187+name = "futures-macro"
1188+version = "0.3.34"
1189+source = "registry+https://github.com/rust-lang/crates.io-index"
1190+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
1191+dependencies = [
1192+ "proc-macro2",
1193+ "quote",
1194+ "syn 3.0.7",
1195+]
1196+
1197+[[package]]
1198+name = "futures-sink"
1199+version = "0.3.34"
1200+source = "registry+https://github.com/rust-lang/crates.io-index"
1201+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
1202+
1203+[[package]]
1204+name = "futures-task"
1205+version = "0.3.34"
1206+source = "registry+https://github.com/rust-lang/crates.io-index"
1207+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
1208+
1209+[[package]]
1210+name = "futures-util"
1211+version = "0.3.34"
1212+source = "registry+https://github.com/rust-lang/crates.io-index"
1213+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
1214+dependencies = [
1215+ "futures-core",
1216+ "futures-io",
1217+ "futures-macro",
1218+ "futures-sink",
1219+ "futures-task",
1220+ "memchr",
1221+ "pin-project-lite",
1222+ "slab",
1223+]
1224+
1225+[[package]]
1226+name = "g1t-desktop"
1227+version = "0.1.0"
1228+dependencies = [
1229+ "reqwest 0.12.28",
1230+ "serde",
1231+ "serde_json",
1232+ "tauri",
1233+ "tauri-build",
1234+ "tauri-plugin-deep-link",
1235+ "tauri-plugin-notification",
1236+ "tauri-plugin-opener",
1237+ "tauri-plugin-single-instance",
1238+ "tauri-plugin-updater",
1239+ "tauri-plugin-window-state",
1240+ "tokio",
1241+ "url",
1242+]
1243+
1244+[[package]]
1245+name = "gdk"
1246+version = "0.18.2"
1247+source = "registry+https://github.com/rust-lang/crates.io-index"
1248+checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
1249+dependencies = [
1250+ "cairo-rs",
1251+ "gdk-pixbuf",
1252+ "gdk-sys",
1253+ "gio",
1254+ "glib",
1255+ "libc",
1256+ "pango",
1257+]
1258+
1259+[[package]]
1260+name = "gdk-pixbuf"
1261+version = "0.18.5"
1262+source = "registry+https://github.com/rust-lang/crates.io-index"
1263+checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
1264+dependencies = [
1265+ "gdk-pixbuf-sys",
1266+ "gio",
1267+ "glib",
1268+ "libc",
1269+ "once_cell",
1270+]
1271+
1272+[[package]]
1273+name = "gdk-pixbuf-sys"
1274+version = "0.18.0"
1275+source = "registry+https://github.com/rust-lang/crates.io-index"
1276+checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
1277+dependencies = [
1278+ "gio-sys",
1279+ "glib-sys",
1280+ "gobject-sys",
1281+ "libc",
1282+ "system-deps",
1283+]
1284+
1285+[[package]]
1286+name = "gdk-sys"
1287+version = "0.18.2"
1288+source = "registry+https://github.com/rust-lang/crates.io-index"
1289+checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
1290+dependencies = [
1291+ "cairo-sys-rs",
1292+ "gdk-pixbuf-sys",
1293+ "gio-sys",
1294+ "glib-sys",
1295+ "gobject-sys",
1296+ "libc",
1297+ "pango-sys",
1298+ "pkg-config",
1299+ "system-deps",
1300+]
1301+
1302+[[package]]
1303+name = "gdkwayland-sys"
1304+version = "0.18.2"
1305+source = "registry+https://github.com/rust-lang/crates.io-index"
1306+checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
1307+dependencies = [
1308+ "gdk-sys",
1309+ "glib-sys",
1310+ "gobject-sys",
1311+ "libc",
1312+ "pkg-config",
1313+ "system-deps",
1314+]
1315+
1316+[[package]]
1317+name = "gdkx11"
1318+version = "0.18.2"
1319+source = "registry+https://github.com/rust-lang/crates.io-index"
1320+checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
1321+dependencies = [
1322+ "gdk",
1323+ "gdkx11-sys",
1324+ "gio",
1325+ "glib",
1326+ "libc",
1327+ "x11",
1328+]
1329+
1330+[[package]]
1331+name = "gdkx11-sys"
1332+version = "0.18.2"
1333+source = "registry+https://github.com/rust-lang/crates.io-index"
1334+checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
1335+dependencies = [
1336+ "gdk-sys",
1337+ "glib-sys",
1338+ "libc",
1339+ "system-deps",
1340+ "x11",
1341+]
1342+
1343+[[package]]
1344+name = "generic-array"
1345+version = "0.14.7"
1346+source = "registry+https://github.com/rust-lang/crates.io-index"
1347+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
1348+dependencies = [
1349+ "typenum",
1350+ "version_check",
1351+]
1352+
1353+[[package]]
1354+name = "getrandom"
1355+version = "0.2.17"
1356+source = "registry+https://github.com/rust-lang/crates.io-index"
1357+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
1358+dependencies = [
1359+ "cfg-if",
1360+ "js-sys",
1361+ "libc",
1362+ "wasi",
1363+ "wasm-bindgen",
1364+]
1365+
1366+[[package]]
1367+name = "getrandom"
1368+version = "0.3.4"
1369+source = "registry+https://github.com/rust-lang/crates.io-index"
1370+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
1371+dependencies = [
1372+ "cfg-if",
1373+ "libc",
1374+ "r-efi 5.3.0",
1375+ "wasip2",
1376+]
1377+
1378+[[package]]
1379+name = "getrandom"
1380+version = "0.4.3"
1381+source = "registry+https://github.com/rust-lang/crates.io-index"
1382+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
1383+dependencies = [
1384+ "cfg-if",
1385+ "js-sys",
1386+ "libc",
1387+ "r-efi 6.0.0",
1388+ "rand_core 0.10.1",
1389+ "wasm-bindgen",
1390+]
1391+
1392+[[package]]
1393+name = "gio"
1394+version = "0.18.4"
1395+source = "registry+https://github.com/rust-lang/crates.io-index"
1396+checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
1397+dependencies = [
1398+ "futures-channel",
1399+ "futures-core",
1400+ "futures-io",
1401+ "futures-util",
1402+ "gio-sys",
1403+ "glib",
1404+ "libc",
1405+ "once_cell",
1406+ "pin-project-lite",
1407+ "smallvec",
1408+ "thiserror 1.0.69",
1409+]
1410+
1411+[[package]]
1412+name = "gio-sys"
1413+version = "0.18.1"
1414+source = "registry+https://github.com/rust-lang/crates.io-index"
1415+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
1416+dependencies = [
1417+ "glib-sys",
1418+ "gobject-sys",
1419+ "libc",
1420+ "system-deps",
1421+ "winapi",
1422+]
1423+
1424+[[package]]
1425+name = "glib"
1426+version = "0.18.5"
1427+source = "registry+https://github.com/rust-lang/crates.io-index"
1428+checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
1429+dependencies = [
1430+ "bitflags 2.13.2",
1431+ "futures-channel",
1432+ "futures-core",
1433+ "futures-executor",
1434+ "futures-task",
1435+ "futures-util",
1436+ "gio-sys",
1437+ "glib-macros",
1438+ "glib-sys",
1439+ "gobject-sys",
1440+ "libc",
1441+ "memchr",
1442+ "once_cell",
1443+ "smallvec",
1444+ "thiserror 1.0.69",
1445+]
1446+
1447+[[package]]
1448+name = "glib-macros"
1449+version = "0.18.5"
1450+source = "registry+https://github.com/rust-lang/crates.io-index"
1451+checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
1452+dependencies = [
1453+ "heck 0.4.1",
1454+ "proc-macro-crate 2.0.2",
1455+ "proc-macro-error",
1456+ "proc-macro2",
1457+ "quote",
1458+ "syn 2.0.119",
1459+]
1460+
1461+[[package]]
1462+name = "glib-sys"
1463+version = "0.18.1"
1464+source = "registry+https://github.com/rust-lang/crates.io-index"
1465+checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
1466+dependencies = [
1467+ "libc",
1468+ "system-deps",
1469+]
1470+
1471+[[package]]
1472+name = "glob"
1473+version = "0.3.4"
1474+source = "registry+https://github.com/rust-lang/crates.io-index"
1475+checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
1476+
1477+[[package]]
1478+name = "gobject-sys"
1479+version = "0.18.0"
1480+source = "registry+https://github.com/rust-lang/crates.io-index"
1481+checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
1482+dependencies = [
1483+ "glib-sys",
1484+ "libc",
1485+ "system-deps",
1486+]
1487+
1488+[[package]]
1489+name = "gtk"
1490+version = "0.18.2"
1491+source = "registry+https://github.com/rust-lang/crates.io-index"
1492+checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
1493+dependencies = [
1494+ "atk",
1495+ "cairo-rs",
1496+ "field-offset",
1497+ "futures-channel",
1498+ "gdk",
1499+ "gdk-pixbuf",
1500+ "gio",
1501+ "glib",
1502+ "gtk-sys",
1503+ "gtk3-macros",
1504+ "libc",
1505+ "pango",
1506+ "pkg-config",
1507+]
1508+
1509+[[package]]
1510+name = "gtk-sys"
1511+version = "0.18.2"
1512+source = "registry+https://github.com/rust-lang/crates.io-index"
1513+checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
1514+dependencies = [
1515+ "atk-sys",
1516+ "cairo-sys-rs",
1517+ "gdk-pixbuf-sys",
1518+ "gdk-sys",
1519+ "gio-sys",
1520+ "glib-sys",
1521+ "gobject-sys",
1522+ "libc",
1523+ "pango-sys",
1524+ "system-deps",
1525+]
1526+
1527+[[package]]
1528+name = "gtk3-macros"
1529+version = "0.18.2"
1530+source = "registry+https://github.com/rust-lang/crates.io-index"
1531+checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
1532+dependencies = [
1533+ "proc-macro-crate 1.3.1",
1534+ "proc-macro-error",
1535+ "proc-macro2",
1536+ "quote",
1537+ "syn 2.0.119",
1538+]
1539+
1540+[[package]]
1541+name = "hashbrown"
1542+version = "0.12.3"
1543+source = "registry+https://github.com/rust-lang/crates.io-index"
1544+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1545+
1546+[[package]]
1547+name = "hashbrown"
1548+version = "0.14.5"
1549+source = "registry+https://github.com/rust-lang/crates.io-index"
1550+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
1551+
1552+[[package]]
1553+name = "hashbrown"
1554+version = "0.17.1"
1555+source = "registry+https://github.com/rust-lang/crates.io-index"
1556+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
1557+
1558+[[package]]
1559+name = "heck"
1560+version = "0.4.1"
1561+source = "registry+https://github.com/rust-lang/crates.io-index"
1562+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
1563+
1564+[[package]]
1565+name = "heck"
1566+version = "0.5.0"
1567+source = "registry+https://github.com/rust-lang/crates.io-index"
1568+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
1569+
1570+[[package]]
1571+name = "hermit-abi"
1572+version = "0.5.3"
1573+source = "registry+https://github.com/rust-lang/crates.io-index"
1574+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
1575+
1576+[[package]]
1577+name = "hex"
1578+version = "0.4.3"
1579+source = "registry+https://github.com/rust-lang/crates.io-index"
1580+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1581+
1582+[[package]]
1583+name = "html5ever"
1584+version = "0.39.0"
1585+source = "registry+https://github.com/rust-lang/crates.io-index"
1586+checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
1587+dependencies = [
1588+ "log",
1589+ "markup5ever",
1590+]
1591+
1592+[[package]]
1593+name = "http"
1594+version = "1.5.0"
1595+source = "registry+https://github.com/rust-lang/crates.io-index"
1596+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
1597+dependencies = [
1598+ "bytes",
1599+ "itoa",
1600+]
1601+
1602+[[package]]
1603+name = "http-body"
1604+version = "1.1.0"
1605+source = "registry+https://github.com/rust-lang/crates.io-index"
1606+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
1607+dependencies = [
1608+ "bytes",
1609+ "http",
1610+]
1611+
1612+[[package]]
1613+name = "http-body-util"
1614+version = "0.1.5"
1615+source = "registry+https://github.com/rust-lang/crates.io-index"
1616+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
1617+dependencies = [
1618+ "bytes",
1619+ "futures-core",
1620+ "http",
1621+ "http-body",
1622+ "pin-project-lite",
1623+]
1624+
1625+[[package]]
1626+name = "httparse"
1627+version = "1.10.1"
1628+source = "registry+https://github.com/rust-lang/crates.io-index"
1629+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1630+
1631+[[package]]
1632+name = "hyper"
1633+version = "1.12.0"
1634+source = "registry+https://github.com/rust-lang/crates.io-index"
1635+checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c"
1636+dependencies = [
1637+ "atomic-waker",
1638+ "bytes",
1639+ "futures-core",
1640+ "http",
1641+ "http-body",
1642+ "httparse",
1643+ "itoa",
1644+ "pin-project-lite",
1645+ "smallvec",
1646+ "tokio",
1647+ "want",
1648+]
1649+
1650+[[package]]
1651+name = "hyper-rustls"
1652+version = "0.27.10"
1653+source = "registry+https://github.com/rust-lang/crates.io-index"
1654+checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
1655+dependencies = [
1656+ "http",
1657+ "hyper",
1658+ "hyper-util",
1659+ "rustls",
1660+ "tokio",
1661+ "tokio-rustls",
1662+ "tower-service",
1663+ "webpki-roots",
1664+]
1665+
1666+[[package]]
1667+name = "hyper-util"
1668+version = "0.1.21"
1669+source = "registry+https://github.com/rust-lang/crates.io-index"
1670+checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
1671+dependencies = [
1672+ "base64 0.23.1",
1673+ "bytes",
1674+ "futures-channel",
1675+ "futures-util",
1676+ "http",
1677+ "http-body",
1678+ "httparse",
1679+ "hyper",
1680+ "ipnet",
1681+ "libc",
1682+ "percent-encoding",
1683+ "pin-project-lite",
1684+ "socket2",
1685+ "system-configuration",
1686+ "tokio",
1687+ "tower-service",
1688+ "tracing",
1689+ "windows-registry",
1690+]
1691+
1692+[[package]]
1693+name = "iana-time-zone"
1694+version = "0.1.65"
1695+source = "registry+https://github.com/rust-lang/crates.io-index"
1696+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
1697+dependencies = [
1698+ "android_system_properties",
1699+ "core-foundation-sys",
1700+ "iana-time-zone-haiku",
1701+ "js-sys",
1702+ "log",
1703+ "wasm-bindgen",
1704+ "windows-core",
1705+]
1706+
1707+[[package]]
1708+name = "iana-time-zone-haiku"
1709+version = "0.1.2"
1710+source = "registry+https://github.com/rust-lang/crates.io-index"
1711+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
1712+dependencies = [
1713+ "cc",
1714+]
1715+
1716+[[package]]
1717+name = "ico"
1718+version = "0.5.0"
1719+source = "registry+https://github.com/rust-lang/crates.io-index"
1720+checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
1721+dependencies = [
1722+ "byteorder",
1723+ "png 0.17.16",
1724+]
1725+
1726+[[package]]
1727+name = "icu_collections"
1728+version = "2.3.0"
1729+source = "registry+https://github.com/rust-lang/crates.io-index"
1730+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
1731+dependencies = [
1732+ "displaydoc",
1733+ "potential_utf",
1734+ "utf8_iter",
1735+ "yoke",
1736+ "zerofrom",
1737+ "zerovec",
1738+]
1739+
1740+[[package]]
1741+name = "icu_locale_core"
1742+version = "2.3.0"
1743+source = "registry+https://github.com/rust-lang/crates.io-index"
1744+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
1745+dependencies = [
1746+ "displaydoc",
1747+ "litemap",
1748+ "tinystr",
1749+ "writeable",
1750+ "zerovec",
1751+]
1752+
1753+[[package]]
1754+name = "icu_normalizer"
1755+version = "2.3.0"
1756+source = "registry+https://github.com/rust-lang/crates.io-index"
1757+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
1758+dependencies = [
1759+ "icu_collections",
1760+ "icu_normalizer_data",
1761+ "icu_properties",
1762+ "icu_provider",
1763+ "smallvec",
1764+ "zerovec",
1765+]
1766+
1767+[[package]]
1768+name = "icu_normalizer_data"
1769+version = "2.3.0"
1770+source = "registry+https://github.com/rust-lang/crates.io-index"
1771+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
1772+
1773+[[package]]
1774+name = "icu_properties"
1775+version = "2.3.0"
1776+source = "registry+https://github.com/rust-lang/crates.io-index"
1777+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
1778+dependencies = [
1779+ "displaydoc",
1780+ "icu_collections",
1781+ "icu_locale_core",
1782+ "icu_properties_data",
1783+ "icu_provider",
1784+ "zerotrie",
1785+ "zerovec",
1786+]
1787+
1788+[[package]]
1789+name = "icu_properties_data"
1790+version = "2.3.0"
1791+source = "registry+https://github.com/rust-lang/crates.io-index"
1792+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
1793+
1794+[[package]]
1795+name = "icu_provider"
1796+version = "2.3.1"
1797+source = "registry+https://github.com/rust-lang/crates.io-index"
1798+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
1799+dependencies = [
1800+ "displaydoc",
1801+ "icu_locale_core",
1802+ "writeable",
1803+ "yoke",
1804+ "zerofrom",
1805+ "zerotrie",
1806+ "zerovec",
1807+]
1808+
1809+[[package]]
1810+name = "ident_case"
1811+version = "1.0.1"
1812+source = "registry+https://github.com/rust-lang/crates.io-index"
1813+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1814+
1815+[[package]]
1816+name = "idna"
1817+version = "1.1.0"
1818+source = "registry+https://github.com/rust-lang/crates.io-index"
1819+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
1820+dependencies = [
1821+ "idna_adapter",
1822+ "smallvec",
1823+ "utf8_iter",
1824+]
1825+
1826+[[package]]
1827+name = "idna_adapter"
1828+version = "1.2.2"
1829+source = "registry+https://github.com/rust-lang/crates.io-index"
1830+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
1831+dependencies = [
1832+ "icu_normalizer",
1833+ "icu_properties",
1834+]
1835+
1836+[[package]]
1837+name = "image"
1838+version = "0.25.10"
1839+source = "registry+https://github.com/rust-lang/crates.io-index"
1840+checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
1841+dependencies = [
1842+ "bytemuck",
1843+ "byteorder-lite",
1844+ "moxcms",
1845+ "num-traits",
1846+ "png 0.18.1",
1847+]
1848+
1849+[[package]]
1850+name = "indexmap"
1851+version = "1.9.3"
1852+source = "registry+https://github.com/rust-lang/crates.io-index"
1853+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1854+dependencies = [
1855+ "autocfg",
1856+ "hashbrown 0.12.3",
1857+ "serde",
1858+]
1859+
1860+[[package]]
1861+name = "indexmap"
1862+version = "2.14.2"
1863+source = "registry+https://github.com/rust-lang/crates.io-index"
1864+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
1865+dependencies = [
1866+ "equivalent",
1867+ "hashbrown 0.17.1",
1868+ "serde",
1869+ "serde_core",
1870+]
1871+
1872+[[package]]
1873+name = "infer"
1874+version = "0.22.0"
1875+source = "registry+https://github.com/rust-lang/crates.io-index"
1876+checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4"
1877+dependencies = [
1878+ "cfb",
1879+]
1880+
1881+[[package]]
1882+name = "ipnet"
1883+version = "2.12.2"
1884+source = "registry+https://github.com/rust-lang/crates.io-index"
1885+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
1886+
1887+[[package]]
1888+name = "is-docker"
1889+version = "0.2.0"
1890+source = "registry+https://github.com/rust-lang/crates.io-index"
1891+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1892+dependencies = [
1893+ "once_cell",
1894+]
1895+
1896+[[package]]
1897+name = "is-wsl"
1898+version = "0.4.0"
1899+source = "registry+https://github.com/rust-lang/crates.io-index"
1900+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1901+dependencies = [
1902+ "is-docker",
1903+ "once_cell",
1904+]
1905+
1906+[[package]]
1907+name = "itoa"
1908+version = "1.0.18"
1909+source = "registry+https://github.com/rust-lang/crates.io-index"
1910+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
1911+
1912+[[package]]
1913+name = "javascriptcore-rs"
1914+version = "1.1.2"
1915+source = "registry+https://github.com/rust-lang/crates.io-index"
1916+checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc"
1917+dependencies = [
1918+ "bitflags 1.3.2",
1919+ "glib",
1920+ "javascriptcore-rs-sys",
1921+]
1922+
1923+[[package]]
1924+name = "javascriptcore-rs-sys"
1925+version = "1.1.1"
1926+source = "registry+https://github.com/rust-lang/crates.io-index"
1927+checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124"
1928+dependencies = [
1929+ "glib-sys",
1930+ "gobject-sys",
1931+ "libc",
1932+ "system-deps",
1933+]
1934+
1935+[[package]]
1936+name = "jiff"
1937+version = "0.2.38"
1938+source = "registry+https://github.com/rust-lang/crates.io-index"
1939+checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383"
1940+dependencies = [
1941+ "defmt",
1942+ "jiff-core",
1943+ "jiff-static",
1944+ "jiff-tzdb-platform",
1945+ "log",
1946+ "portable-atomic",
1947+ "portable-atomic-util",
1948+ "serde_core",
1949+ "windows-link",
1950+]
1951+
1952+[[package]]
1953+name = "jiff-core"
1954+version = "0.1.1"
1955+source = "registry+https://github.com/rust-lang/crates.io-index"
1956+checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
1957+dependencies = [
1958+ "defmt",
1959+ "log",
1960+]
1961+
1962+[[package]]
1963+name = "jiff-static"
1964+version = "0.2.38"
1965+source = "registry+https://github.com/rust-lang/crates.io-index"
1966+checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c"
1967+dependencies = [
1968+ "jiff-core",
1969+ "proc-macro2",
1970+ "quote",
1971+ "syn 2.0.119",
1972+]
1973+
1974+[[package]]
1975+name = "jiff-tzdb"
1976+version = "0.1.9"
1977+source = "registry+https://github.com/rust-lang/crates.io-index"
1978+checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7"
1979+
1980+[[package]]
1981+name = "jiff-tzdb-platform"
1982+version = "0.1.3"
1983+source = "registry+https://github.com/rust-lang/crates.io-index"
1984+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
1985+dependencies = [
1986+ "jiff-tzdb",
1987+]
1988+
1989+[[package]]
1990+name = "jni"
1991+version = "0.21.1"
1992+source = "registry+https://github.com/rust-lang/crates.io-index"
1993+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
1994+dependencies = [
1995+ "cesu8",
1996+ "cfg-if",
1997+ "combine",
1998+ "jni-sys 0.3.1",
1999+ "log",
2000+ "thiserror 1.0.69",
2001+ "walkdir",
2002+ "windows-sys 0.45.0",
2003+]
2004+
2005+[[package]]
2006+name = "jni"
2007+version = "0.22.4"
2008+source = "registry+https://github.com/rust-lang/crates.io-index"
2009+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
2010+dependencies = [
2011+ "cfg-if",
2012+ "combine",
2013+ "jni-macros",
2014+ "jni-sys 0.4.1",
2015+ "log",
2016+ "simd_cesu8",
2017+ "thiserror 2.0.21",
2018+ "walkdir",
2019+ "windows-link",
2020+]
2021+
2022+[[package]]
2023+name = "jni-macros"
2024+version = "0.22.4"
2025+source = "registry+https://github.com/rust-lang/crates.io-index"
2026+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
2027+dependencies = [
2028+ "proc-macro2",
2029+ "quote",
2030+ "rustc_version",
2031+ "simd_cesu8",
2032+ "syn 2.0.119",
2033+]
2034+
2035+[[package]]
2036+name = "jni-sys"
2037+version = "0.3.1"
2038+source = "registry+https://github.com/rust-lang/crates.io-index"
2039+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
2040+dependencies = [
2041+ "jni-sys 0.4.1",
2042+]
2043+
2044+[[package]]
2045+name = "jni-sys"
2046+version = "0.4.1"
2047+source = "registry+https://github.com/rust-lang/crates.io-index"
2048+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
2049+dependencies = [
2050+ "jni-sys-macros",
2051+]
2052+
2053+[[package]]
2054+name = "jni-sys-macros"
2055+version = "0.4.1"
2056+source = "registry+https://github.com/rust-lang/crates.io-index"
2057+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
2058+dependencies = [
2059+ "quote",
2060+ "syn 2.0.119",
2061+]
2062+
2063+[[package]]
2064+name = "js-sys"
2065+version = "0.3.106"
2066+source = "registry+https://github.com/rust-lang/crates.io-index"
2067+checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
2068+dependencies = [
2069+ "cfg-if",
2070+ "futures-util",
2071+ "wasm-bindgen",
2072+]
2073+
2074+[[package]]
2075+name = "json-patch"
2076+version = "4.2.0"
2077+source = "registry+https://github.com/rust-lang/crates.io-index"
2078+checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72"
2079+dependencies = [
2080+ "jsonptr",
2081+ "serde",
2082+ "serde_json",
2083+ "thiserror 2.0.21",
2084+]
2085+
2086+[[package]]
2087+name = "jsonptr"
2088+version = "0.7.1"
2089+source = "registry+https://github.com/rust-lang/crates.io-index"
2090+checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe"
2091+dependencies = [
2092+ "serde",
2093+ "serde_json",
2094+]
2095+
2096+[[package]]
2097+name = "keyboard-types"
2098+version = "0.8.3"
2099+source = "registry+https://github.com/rust-lang/crates.io-index"
2100+checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
2101+dependencies = [
2102+ "bitflags 2.13.2",
2103+ "serde",
2104+]
2105+
2106+[[package]]
2107+name = "libappindicator"
2108+version = "0.9.0"
2109+source = "registry+https://github.com/rust-lang/crates.io-index"
2110+checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a"
2111+dependencies = [
2112+ "glib",
2113+ "gtk",
2114+ "gtk-sys",
2115+ "libappindicator-sys",
2116+ "log",
2117+]
2118+
2119+[[package]]
2120+name = "libappindicator-sys"
2121+version = "0.9.0"
2122+source = "registry+https://github.com/rust-lang/crates.io-index"
2123+checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
2124+dependencies = [
2125+ "gtk-sys",
2126+ "libloading",
2127+ "once_cell",
2128+]
2129+
2130+[[package]]
2131+name = "libc"
2132+version = "0.2.190"
2133+source = "registry+https://github.com/rust-lang/crates.io-index"
2134+checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
2135+
2136+[[package]]
2137+name = "libdbus-sys"
2138+version = "0.2.7"
2139+source = "registry+https://github.com/rust-lang/crates.io-index"
2140+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
2141+dependencies = [
2142+ "pkg-config",
2143+]
2144+
2145+[[package]]
2146+name = "libloading"
2147+version = "0.7.4"
2148+source = "registry+https://github.com/rust-lang/crates.io-index"
2149+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
2150+dependencies = [
2151+ "cfg-if",
2152+ "winapi",
2153+]
2154+
2155+[[package]]
2156+name = "libredox"
2157+version = "0.1.25"
2158+source = "registry+https://github.com/rust-lang/crates.io-index"
2159+checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
2160+dependencies = [
2161+ "libc",
2162+]
2163+
2164+[[package]]
2165+name = "linux-raw-sys"
2166+version = "0.12.1"
2167+source = "registry+https://github.com/rust-lang/crates.io-index"
2168+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
2169+
2170+[[package]]
2171+name = "litemap"
2172+version = "0.8.3"
2173+source = "registry+https://github.com/rust-lang/crates.io-index"
2174+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
2175+
2176+[[package]]
2177+name = "lock_api"
2178+version = "0.4.14"
2179+source = "registry+https://github.com/rust-lang/crates.io-index"
2180+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
2181+dependencies = [
2182+ "scopeguard",
2183+]
2184+
2185+[[package]]
2186+name = "log"
2187+version = "0.4.34"
2188+source = "registry+https://github.com/rust-lang/crates.io-index"
2189+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
2190+
2191+[[package]]
2192+name = "lru-slab"
2193+version = "0.1.3"
2194+source = "registry+https://github.com/rust-lang/crates.io-index"
2195+checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
2196+
2197+[[package]]
2198+name = "mac-notification-sys"
2199+version = "0.6.15"
2200+source = "registry+https://github.com/rust-lang/crates.io-index"
2201+checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
2202+dependencies = [
2203+ "cc",
2204+ "log",
2205+ "objc2",
2206+ "objc2-foundation",
2207+ "time",
2208+ "uuid",
2209+]
2210+
2211+[[package]]
2212+name = "markup5ever"
2213+version = "0.39.0"
2214+source = "registry+https://github.com/rust-lang/crates.io-index"
2215+checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
2216+dependencies = [
2217+ "log",
2218+ "tendril",
2219+ "web_atoms",
2220+]
2221+
2222+[[package]]
2223+name = "memchr"
2224+version = "2.8.3"
2225+source = "registry+https://github.com/rust-lang/crates.io-index"
2226+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
2227+
2228+[[package]]
2229+name = "memoffset"
2230+version = "0.9.1"
2231+source = "registry+https://github.com/rust-lang/crates.io-index"
2232+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
2233+dependencies = [
2234+ "autocfg",
2235+]
2236+
2237+[[package]]
2238+name = "mime"
2239+version = "0.3.17"
2240+source = "registry+https://github.com/rust-lang/crates.io-index"
2241+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
2242+
2243+[[package]]
2244+name = "minisign-verify"
2245+version = "0.3.0"
2246+source = "registry+https://github.com/rust-lang/crates.io-index"
2247+checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce"
2248+
2249+[[package]]
2250+name = "miniz_oxide"
2251+version = "0.8.9"
2252+source = "registry+https://github.com/rust-lang/crates.io-index"
2253+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
2254+dependencies = [
2255+ "adler2",
2256+ "simd-adler32",
2257+]
2258+
2259+[[package]]
2260+name = "miniz_oxide"
2261+version = "0.9.1"
2262+source = "registry+https://github.com/rust-lang/crates.io-index"
2263+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
2264+dependencies = [
2265+ "adler2",
2266+ "simd-adler32",
2267+]
2268+
2269+[[package]]
2270+name = "mio"
2271+version = "1.2.4"
2272+source = "registry+https://github.com/rust-lang/crates.io-index"
2273+checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
2274+dependencies = [
2275+ "libc",
2276+ "wasi",
2277+ "windows-sys 0.61.2",
2278+]
2279+
2280+[[package]]
2281+name = "moxcms"
2282+version = "0.8.1"
2283+source = "registry+https://github.com/rust-lang/crates.io-index"
2284+checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
2285+dependencies = [
2286+ "num-traits",
2287+ "pxfm",
2288+]
2289+
2290+[[package]]
2291+name = "muda"
2292+version = "0.20.0"
2293+source = "registry+https://github.com/rust-lang/crates.io-index"
2294+checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996"
2295+dependencies = [
2296+ "crossbeam-channel",
2297+ "dpi",
2298+ "gtk",
2299+ "keyboard-types",
2300+ "objc2",
2301+ "objc2-app-kit",
2302+ "objc2-core-foundation",
2303+ "objc2-foundation",
2304+ "once_cell",
2305+ "png 0.18.1",
2306+ "serde",
2307+ "thiserror 2.0.21",
2308+ "windows-sys 0.61.2",
2309+]
2310+
2311+[[package]]
2312+name = "ndk"
2313+version = "0.9.0"
2314+source = "registry+https://github.com/rust-lang/crates.io-index"
2315+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
2316+dependencies = [
2317+ "bitflags 2.13.2",
2318+ "jni-sys 0.3.1",
2319+ "log",
2320+ "ndk-sys",
2321+ "num_enum",
2322+ "raw-window-handle",
2323+ "thiserror 1.0.69",
2324+]
2325+
2326+[[package]]
2327+name = "ndk-context"
2328+version = "0.1.1"
2329+source = "registry+https://github.com/rust-lang/crates.io-index"
2330+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
2331+
2332+[[package]]
2333+name = "ndk-sys"
2334+version = "0.6.0+11769913"
2335+source = "registry+https://github.com/rust-lang/crates.io-index"
2336+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
2337+dependencies = [
2338+ "jni-sys 0.3.1",
2339+]
2340+
2341+[[package]]
2342+name = "new_debug_unreachable"
2343+version = "1.0.6"
2344+source = "registry+https://github.com/rust-lang/crates.io-index"
2345+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2346+
2347+[[package]]
2348+name = "notify-rust"
2349+version = "4.18.2"
2350+source = "registry+https://github.com/rust-lang/crates.io-index"
2351+checksum = "4587364a9a0074333429b3df75a30a205340c56a536ca3eb6ca0e59b87bbf8af"
2352+dependencies = [
2353+ "futures-lite",
2354+ "log",
2355+ "mac-notification-sys",
2356+ "serde",
2357+ "tauri-winrt-notification",
2358+ "zbus",
2359+]
2360+
2361+[[package]]
2362+name = "num-conv"
2363+version = "0.2.2"
2364+source = "registry+https://github.com/rust-lang/crates.io-index"
2365+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
2366+
2367+[[package]]
2368+name = "num-traits"
2369+version = "0.2.19"
2370+source = "registry+https://github.com/rust-lang/crates.io-index"
2371+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
2372+dependencies = [
2373+ "autocfg",
2374+]
2375+
2376+[[package]]
2377+name = "num_enum"
2378+version = "0.7.6"
2379+source = "registry+https://github.com/rust-lang/crates.io-index"
2380+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
2381+dependencies = [
2382+ "num_enum_derive",
2383+ "rustversion",
2384+]
2385+
2386+[[package]]
2387+name = "num_enum_derive"
2388+version = "0.7.6"
2389+source = "registry+https://github.com/rust-lang/crates.io-index"
2390+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
2391+dependencies = [
2392+ "proc-macro-crate 3.5.0",
2393+ "proc-macro2",
2394+ "quote",
2395+ "syn 2.0.119",
2396+]
2397+
2398+[[package]]
2399+name = "objc2"
2400+version = "0.6.5"
2401+source = "registry+https://github.com/rust-lang/crates.io-index"
2402+checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
2403+dependencies = [
2404+ "objc2-encode",
2405+ "objc2-exception-helper",
2406+]
2407+
2408+[[package]]
2409+name = "objc2-app-kit"
2410+version = "0.3.2"
2411+source = "registry+https://github.com/rust-lang/crates.io-index"
2412+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
2413+dependencies = [
2414+ "bitflags 2.13.2",
2415+ "block2",
2416+ "objc2",
2417+ "objc2-core-foundation",
2418+ "objc2-foundation",
2419+ "objc2-quartz-core",
2420+]
2421+
2422+[[package]]
2423+name = "objc2-cloud-kit"
2424+version = "0.3.2"
2425+source = "registry+https://github.com/rust-lang/crates.io-index"
2426+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
2427+dependencies = [
2428+ "bitflags 2.13.2",
2429+ "objc2",
2430+ "objc2-foundation",
2431+]
2432+
2433+[[package]]
2434+name = "objc2-core-data"
2435+version = "0.3.2"
2436+source = "registry+https://github.com/rust-lang/crates.io-index"
2437+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
2438+dependencies = [
2439+ "objc2",
2440+ "objc2-foundation",
2441+]
2442+
2443+[[package]]
2444+name = "objc2-core-foundation"
2445+version = "0.3.2"
2446+source = "registry+https://github.com/rust-lang/crates.io-index"
2447+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
2448+dependencies = [
2449+ "bitflags 2.13.2",
2450+ "dispatch2",
2451+ "objc2",
2452+]
2453+
2454+[[package]]
2455+name = "objc2-core-graphics"
2456+version = "0.3.2"
2457+source = "registry+https://github.com/rust-lang/crates.io-index"
2458+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
2459+dependencies = [
2460+ "bitflags 2.13.2",
2461+ "dispatch2",
2462+ "objc2",
2463+ "objc2-core-foundation",
2464+ "objc2-io-surface",
2465+]
2466+
2467+[[package]]
2468+name = "objc2-core-image"
2469+version = "0.3.2"
2470+source = "registry+https://github.com/rust-lang/crates.io-index"
2471+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
2472+dependencies = [
2473+ "objc2",
2474+ "objc2-foundation",
2475+]
2476+
2477+[[package]]
2478+name = "objc2-core-location"
2479+version = "0.3.2"
2480+source = "registry+https://github.com/rust-lang/crates.io-index"
2481+checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
2482+dependencies = [
2483+ "objc2",
2484+ "objc2-foundation",
2485+]
2486+
2487+[[package]]
2488+name = "objc2-core-text"
2489+version = "0.3.2"
2490+source = "registry+https://github.com/rust-lang/crates.io-index"
2491+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
2492+dependencies = [
2493+ "bitflags 2.13.2",
2494+ "objc2",
2495+ "objc2-core-foundation",
2496+ "objc2-core-graphics",
2497+]
2498+
2499+[[package]]
2500+name = "objc2-encode"
2501+version = "4.1.0"
2502+source = "registry+https://github.com/rust-lang/crates.io-index"
2503+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
2504+
2505+[[package]]
2506+name = "objc2-exception-helper"
2507+version = "0.1.1"
2508+source = "registry+https://github.com/rust-lang/crates.io-index"
2509+checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a"
2510+dependencies = [
2511+ "cc",
2512+]
2513+
2514+[[package]]
2515+name = "objc2-foundation"
2516+version = "0.3.2"
2517+source = "registry+https://github.com/rust-lang/crates.io-index"
2518+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
2519+dependencies = [
2520+ "bitflags 2.13.2",
2521+ "block2",
2522+ "libc",
2523+ "objc2",
2524+ "objc2-core-foundation",
2525+]
2526+
2527+[[package]]
2528+name = "objc2-io-surface"
2529+version = "0.3.2"
2530+source = "registry+https://github.com/rust-lang/crates.io-index"
2531+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
2532+dependencies = [
2533+ "bitflags 2.13.2",
2534+ "objc2",
2535+ "objc2-core-foundation",
2536+]
2537+
2538+[[package]]
2539+name = "objc2-osa-kit"
2540+version = "0.3.2"
2541+source = "registry+https://github.com/rust-lang/crates.io-index"
2542+checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
2543+dependencies = [
2544+ "bitflags 2.13.2",
2545+ "objc2",
2546+ "objc2-app-kit",
2547+ "objc2-foundation",
2548+]
2549+
2550+[[package]]
2551+name = "objc2-quartz-core"
2552+version = "0.3.2"
2553+source = "registry+https://github.com/rust-lang/crates.io-index"
2554+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
2555+dependencies = [
2556+ "bitflags 2.13.2",
2557+ "objc2",
2558+ "objc2-core-foundation",
2559+ "objc2-foundation",
2560+]
2561+
2562+[[package]]
2563+name = "objc2-ui-kit"
2564+version = "0.3.2"
2565+source = "registry+https://github.com/rust-lang/crates.io-index"
2566+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
2567+dependencies = [
2568+ "bitflags 2.13.2",
2569+ "block2",
2570+ "objc2",
2571+ "objc2-cloud-kit",
2572+ "objc2-core-data",
2573+ "objc2-core-foundation",
2574+ "objc2-core-graphics",
2575+ "objc2-core-image",
2576+ "objc2-core-location",
2577+ "objc2-core-text",
2578+ "objc2-foundation",
2579+ "objc2-quartz-core",
2580+ "objc2-user-notifications",
2581+]
2582+
2583+[[package]]
2584+name = "objc2-user-notifications"
2585+version = "0.3.2"
2586+source = "registry+https://github.com/rust-lang/crates.io-index"
2587+checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
2588+dependencies = [
2589+ "objc2",
2590+ "objc2-foundation",
2591+]
2592+
2593+[[package]]
2594+name = "objc2-web-kit"
2595+version = "0.3.2"
2596+source = "registry+https://github.com/rust-lang/crates.io-index"
2597+checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
2598+dependencies = [
2599+ "bitflags 2.13.2",
2600+ "block2",
2601+ "objc2",
2602+ "objc2-app-kit",
2603+ "objc2-core-foundation",
2604+ "objc2-foundation",
2605+]
2606+
2607+[[package]]
2608+name = "once_cell"
2609+version = "1.21.4"
2610+source = "registry+https://github.com/rust-lang/crates.io-index"
2611+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
2612+
2613+[[package]]
2614+name = "open"
2615+version = "5.4.4"
2616+source = "registry+https://github.com/rust-lang/crates.io-index"
2617+checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
2618+dependencies = [
2619+ "dunce",
2620+ "is-wsl",
2621+ "libc",
2622+]
2623+
2624+[[package]]
2625+name = "openssl-probe"
2626+version = "0.2.1"
2627+source = "registry+https://github.com/rust-lang/crates.io-index"
2628+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
2629+
2630+[[package]]
2631+name = "option-ext"
2632+version = "0.2.0"
2633+source = "registry+https://github.com/rust-lang/crates.io-index"
2634+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
2635+
2636+[[package]]
2637+name = "ordered-multimap"
2638+version = "0.7.3"
2639+source = "registry+https://github.com/rust-lang/crates.io-index"
2640+checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
2641+dependencies = [
2642+ "dlv-list",
2643+ "hashbrown 0.14.5",
2644+]
2645+
2646+[[package]]
2647+name = "ordered-stream"
2648+version = "0.2.0"
2649+source = "registry+https://github.com/rust-lang/crates.io-index"
2650+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
2651+dependencies = [
2652+ "futures-core",
2653+ "pin-project-lite",
2654+]
2655+
2656+[[package]]
2657+name = "osakit"
2658+version = "0.3.1"
2659+source = "registry+https://github.com/rust-lang/crates.io-index"
2660+checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
2661+dependencies = [
2662+ "objc2",
2663+ "objc2-foundation",
2664+ "objc2-osa-kit",
2665+ "serde",
2666+ "serde_json",
2667+ "thiserror 2.0.21",
2668+]
2669+
2670+[[package]]
2671+name = "pango"
2672+version = "0.18.3"
2673+source = "registry+https://github.com/rust-lang/crates.io-index"
2674+checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
2675+dependencies = [
2676+ "gio",
2677+ "glib",
2678+ "libc",
2679+ "once_cell",
2680+ "pango-sys",
2681+]
2682+
2683+[[package]]
2684+name = "pango-sys"
2685+version = "0.18.0"
2686+source = "registry+https://github.com/rust-lang/crates.io-index"
2687+checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
2688+dependencies = [
2689+ "glib-sys",
2690+ "gobject-sys",
2691+ "libc",
2692+ "system-deps",
2693+]
2694+
2695+[[package]]
2696+name = "parking"
2697+version = "2.2.1"
2698+source = "registry+https://github.com/rust-lang/crates.io-index"
2699+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
2700+
2701+[[package]]
2702+name = "parking_lot"
2703+version = "0.12.5"
2704+source = "registry+https://github.com/rust-lang/crates.io-index"
2705+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
2706+dependencies = [
2707+ "lock_api",
2708+ "parking_lot_core",
2709+]
2710+
2711+[[package]]
2712+name = "parking_lot_core"
2713+version = "0.9.12"
2714+source = "registry+https://github.com/rust-lang/crates.io-index"
2715+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
2716+dependencies = [
2717+ "cfg-if",
2718+ "libc",
2719+ "redox_syscall",
2720+ "smallvec",
2721+ "windows-link",
2722+]
2723+
2724+[[package]]
2725+name = "percent-encoding"
2726+version = "2.3.2"
2727+source = "registry+https://github.com/rust-lang/crates.io-index"
2728+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
2729+
2730+[[package]]
2731+name = "phf"
2732+version = "0.13.1"
2733+source = "registry+https://github.com/rust-lang/crates.io-index"
2734+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
2735+dependencies = [
2736+ "phf_macros",
2737+ "phf_shared",
2738+ "serde",
2739+]
2740+
2741+[[package]]
2742+name = "phf_codegen"
2743+version = "0.13.1"
2744+source = "registry+https://github.com/rust-lang/crates.io-index"
2745+checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
2746+dependencies = [
2747+ "phf_generator",
2748+ "phf_shared",
2749+]
2750+
2751+[[package]]
2752+name = "phf_generator"
2753+version = "0.13.1"
2754+source = "registry+https://github.com/rust-lang/crates.io-index"
2755+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
2756+dependencies = [
2757+ "fastrand",
2758+ "phf_shared",
2759+]
2760+
2761+[[package]]
2762+name = "phf_macros"
2763+version = "0.13.1"
2764+source = "registry+https://github.com/rust-lang/crates.io-index"
2765+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
2766+dependencies = [
2767+ "phf_generator",
2768+ "phf_shared",
2769+ "proc-macro2",
2770+ "quote",
2771+ "syn 2.0.119",
2772+]
2773+
2774+[[package]]
2775+name = "phf_shared"
2776+version = "0.13.1"
2777+source = "registry+https://github.com/rust-lang/crates.io-index"
2778+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
2779+dependencies = [
2780+ "siphasher",
2781+]
2782+
2783+[[package]]
2784+name = "pin-project-lite"
2785+version = "0.2.17"
2786+source = "registry+https://github.com/rust-lang/crates.io-index"
2787+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
2788+
2789+[[package]]
2790+name = "piper"
2791+version = "0.2.5"
2792+source = "registry+https://github.com/rust-lang/crates.io-index"
2793+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
2794+dependencies = [
2795+ "atomic-waker",
2796+ "fastrand",
2797+ "futures-io",
2798+]
2799+
2800+[[package]]
2801+name = "pkg-config"
2802+version = "0.3.34"
2803+source = "registry+https://github.com/rust-lang/crates.io-index"
2804+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
2805+
2806+[[package]]
2807+name = "plist"
2808+version = "1.10.1"
2809+source = "registry+https://github.com/rust-lang/crates.io-index"
2810+checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb"
2811+dependencies = [
2812+ "base64 0.23.1",
2813+ "indexmap 2.14.2",
2814+ "quick-xml",
2815+ "serde",
2816+ "time",
2817+]
2818+
2819+[[package]]
2820+name = "png"
2821+version = "0.17.16"
2822+source = "registry+https://github.com/rust-lang/crates.io-index"
2823+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
2824+dependencies = [
2825+ "bitflags 1.3.2",
2826+ "crc32fast",
2827+ "fdeflate",
2828+ "flate2",
2829+ "miniz_oxide 0.8.9",
2830+]
2831+
2832+[[package]]
2833+name = "png"
2834+version = "0.18.1"
2835+source = "registry+https://github.com/rust-lang/crates.io-index"
2836+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
2837+dependencies = [
2838+ "bitflags 2.13.2",
2839+ "crc32fast",
2840+ "fdeflate",
2841+ "flate2",
2842+ "miniz_oxide 0.8.9",
2843+]
2844+
2845+[[package]]
2846+name = "polling"
2847+version = "3.11.0"
2848+source = "registry+https://github.com/rust-lang/crates.io-index"
2849+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
2850+dependencies = [
2851+ "cfg-if",
2852+ "concurrent-queue",
2853+ "hermit-abi",
2854+ "pin-project-lite",
2855+ "rustix",
2856+ "windows-sys 0.61.2",
2857+]
2858+
2859+[[package]]
2860+name = "portable-atomic"
2861+version = "1.15.0"
2862+source = "registry+https://github.com/rust-lang/crates.io-index"
2863+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
2864+
2865+[[package]]
2866+name = "portable-atomic-util"
2867+version = "0.2.8"
2868+source = "registry+https://github.com/rust-lang/crates.io-index"
2869+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
2870+dependencies = [
2871+ "portable-atomic",
2872+]
2873+
2874+[[package]]
2875+name = "potential_utf"
2876+version = "0.1.6"
2877+source = "registry+https://github.com/rust-lang/crates.io-index"
2878+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
2879+dependencies = [
2880+ "zerovec",
2881+]
2882+
2883+[[package]]
2884+name = "powerfmt"
2885+version = "0.2.1"
2886+source = "registry+https://github.com/rust-lang/crates.io-index"
2887+checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
2888+
2889+[[package]]
2890+name = "ppv-lite86"
2891+version = "0.2.21"
2892+source = "registry+https://github.com/rust-lang/crates.io-index"
2893+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
2894+dependencies = [
2895+ "zerocopy",
2896+]
2897+
2898+[[package]]
2899+name = "precomputed-hash"
2900+version = "0.1.1"
2901+source = "registry+https://github.com/rust-lang/crates.io-index"
2902+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
2903+
2904+[[package]]
2905+name = "proc-macro-crate"
2906+version = "1.3.1"
2907+source = "registry+https://github.com/rust-lang/crates.io-index"
2908+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
2909+dependencies = [
2910+ "once_cell",
2911+ "toml_edit 0.19.15",
2912+]
2913+
2914+[[package]]
2915+name = "proc-macro-crate"
2916+version = "2.0.2"
2917+source = "registry+https://github.com/rust-lang/crates.io-index"
2918+checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
2919+dependencies = [
2920+ "toml_datetime 0.6.3",
2921+ "toml_edit 0.20.2",
2922+]
2923+
2924+[[package]]
2925+name = "proc-macro-crate"
2926+version = "3.5.0"
2927+source = "registry+https://github.com/rust-lang/crates.io-index"
2928+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
2929+dependencies = [
2930+ "toml_edit 0.25.17+spec-1.1.0",
2931+]
2932+
2933+[[package]]
2934+name = "proc-macro-error"
2935+version = "1.0.4"
2936+source = "registry+https://github.com/rust-lang/crates.io-index"
2937+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2938+dependencies = [
2939+ "proc-macro-error-attr",
2940+ "proc-macro2",
2941+ "quote",
2942+ "syn 1.0.109",
2943+ "version_check",
2944+]
2945+
2946+[[package]]
2947+name = "proc-macro-error-attr"
2948+version = "1.0.4"
2949+source = "registry+https://github.com/rust-lang/crates.io-index"
2950+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2951+dependencies = [
2952+ "proc-macro2",
2953+ "quote",
2954+ "version_check",
2955+]
2956+
2957+[[package]]
2958+name = "proc-macro2"
2959+version = "1.0.107"
2960+source = "registry+https://github.com/rust-lang/crates.io-index"
2961+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
2962+dependencies = [
2963+ "unicode-ident",
2964+]
2965+
2966+[[package]]
2967+name = "pxfm"
2968+version = "0.1.30"
2969+source = "registry+https://github.com/rust-lang/crates.io-index"
2970+checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
2971+
2972+[[package]]
2973+name = "quick-xml"
2974+version = "0.42.0"
2975+source = "registry+https://github.com/rust-lang/crates.io-index"
2976+checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
2977+dependencies = [
2978+ "memchr",
2979+]
2980+
2981+[[package]]
2982+name = "quinn"
2983+version = "0.11.12"
2984+source = "registry+https://github.com/rust-lang/crates.io-index"
2985+checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
2986+dependencies = [
2987+ "bytes",
2988+ "cfg_aliases",
2989+ "pin-project-lite",
2990+ "quinn-proto",
2991+ "quinn-udp",
2992+ "rustc-hash",
2993+ "rustls",
2994+ "socket2",
2995+ "thiserror 2.0.21",
2996+ "tokio",
2997+ "tracing",
2998+ "web-time",
2999+]
3000+
3001+[[package]]
3002+name = "quinn-proto"
3003+version = "0.11.19"
3004+source = "registry+https://github.com/rust-lang/crates.io-index"
3005+checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
3006+dependencies = [
3007+ "bytes",
3008+ "getrandom 0.4.3",
3009+ "lru-slab",
3010+ "rand 0.10.3",
3011+ "rand_pcg",
3012+ "ring",
3013+ "rustc-hash",
3014+ "rustls",
3015+ "rustls-pki-types",
3016+ "slab",
3017+ "thiserror 2.0.21",
3018+ "tinyvec",
3019+ "tracing",
3020+ "web-time",
3021+]
3022+
3023+[[package]]
3024+name = "quinn-udp"
3025+version = "0.5.16"
3026+source = "registry+https://github.com/rust-lang/crates.io-index"
3027+checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
3028+dependencies = [
3029+ "cfg_aliases",
3030+ "libc",
3031+ "once_cell",
3032+ "socket2",
3033+ "tracing",
3034+ "windows-sys 0.61.2",
3035+]
3036+
3037+[[package]]
3038+name = "quote"
3039+version = "1.0.47"
3040+source = "registry+https://github.com/rust-lang/crates.io-index"
3041+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
3042+dependencies = [
3043+ "proc-macro2",
3044+]
3045+
3046+[[package]]
3047+name = "r-efi"
3048+version = "5.3.0"
3049+source = "registry+https://github.com/rust-lang/crates.io-index"
3050+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
3051+
3052+[[package]]
3053+name = "r-efi"
3054+version = "6.0.0"
3055+source = "registry+https://github.com/rust-lang/crates.io-index"
3056+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
3057+
3058+[[package]]
3059+name = "rand"
3060+version = "0.9.5"
3061+source = "registry+https://github.com/rust-lang/crates.io-index"
3062+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
3063+dependencies = [
3064+ "rand_chacha",
3065+ "rand_core 0.9.5",
3066+]
3067+
3068+[[package]]
3069+name = "rand"
3070+version = "0.10.3"
3071+source = "registry+https://github.com/rust-lang/crates.io-index"
3072+checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
3073+dependencies = [
3074+ "chacha20",
3075+ "getrandom 0.4.3",
3076+ "rand_core 0.10.1",
3077+]
3078+
3079+[[package]]
3080+name = "rand_chacha"
3081+version = "0.9.0"
3082+source = "registry+https://github.com/rust-lang/crates.io-index"
3083+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
3084+dependencies = [
3085+ "ppv-lite86",
3086+ "rand_core 0.9.5",
3087+]
3088+
3089+[[package]]
3090+name = "rand_core"
3091+version = "0.9.5"
3092+source = "registry+https://github.com/rust-lang/crates.io-index"
3093+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
3094+dependencies = [
3095+ "getrandom 0.3.4",
3096+]
3097+
3098+[[package]]
3099+name = "rand_core"
3100+version = "0.10.1"
3101+source = "registry+https://github.com/rust-lang/crates.io-index"
3102+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
3103+
3104+[[package]]
3105+name = "rand_pcg"
3106+version = "0.10.2"
3107+source = "registry+https://github.com/rust-lang/crates.io-index"
3108+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
3109+dependencies = [
3110+ "rand_core 0.10.1",
3111+]
3112+
3113+[[package]]
3114+name = "raw-window-handle"
3115+version = "0.6.2"
3116+source = "registry+https://github.com/rust-lang/crates.io-index"
3117+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
3118+
3119+[[package]]
3120+name = "redox_syscall"
3121+version = "0.5.18"
3122+source = "registry+https://github.com/rust-lang/crates.io-index"
3123+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3124+dependencies = [
3125+ "bitflags 2.13.2",
3126+]
3127+
3128+[[package]]
3129+name = "redox_users"
3130+version = "0.5.3"
3131+source = "registry+https://github.com/rust-lang/crates.io-index"
3132+checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0"
3133+dependencies = [
3134+ "libredox",
3135+ "thiserror 2.0.21",
3136+]
3137+
3138+[[package]]
3139+name = "ref-cast"
3140+version = "1.0.27"
3141+source = "registry+https://github.com/rust-lang/crates.io-index"
3142+checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
3143+dependencies = [
3144+ "ref-cast-impl",
3145+]
3146+
3147+[[package]]
3148+name = "ref-cast-impl"
3149+version = "1.0.27"
3150+source = "registry+https://github.com/rust-lang/crates.io-index"
3151+checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
3152+dependencies = [
3153+ "proc-macro2",
3154+ "quote",
3155+ "syn 3.0.7",
3156+]
3157+
3158+[[package]]
3159+name = "regex"
3160+version = "1.13.1"
3161+source = "registry+https://github.com/rust-lang/crates.io-index"
3162+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
3163+dependencies = [
3164+ "aho-corasick",
3165+ "memchr",
3166+ "regex-automata",
3167+ "regex-syntax",
3168+]
3169+
3170+[[package]]
3171+name = "regex-automata"
3172+version = "0.4.18"
3173+source = "registry+https://github.com/rust-lang/crates.io-index"
3174+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
3175+dependencies = [
3176+ "aho-corasick",
3177+ "memchr",
3178+ "regex-syntax",
3179+]
3180+
3181+[[package]]
3182+name = "regex-syntax"
3183+version = "0.8.11"
3184+source = "registry+https://github.com/rust-lang/crates.io-index"
3185+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
3186+
3187+[[package]]
3188+name = "reqwest"
3189+version = "0.12.28"
3190+source = "registry+https://github.com/rust-lang/crates.io-index"
3191+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
3192+dependencies = [
3193+ "base64 0.22.1",
3194+ "bytes",
3195+ "futures-core",
3196+ "http",
3197+ "http-body",
3198+ "http-body-util",
3199+ "hyper",
3200+ "hyper-rustls",
3201+ "hyper-util",
3202+ "js-sys",
3203+ "log",
3204+ "percent-encoding",
3205+ "pin-project-lite",
3206+ "quinn",
3207+ "rustls",
3208+ "rustls-pki-types",
3209+ "serde",
3210+ "serde_json",
3211+ "serde_urlencoded",
3212+ "sync_wrapper",
3213+ "tokio",
3214+ "tokio-rustls",
3215+ "tower",
3216+ "tower-http",
3217+ "tower-service",
3218+ "url",
3219+ "wasm-bindgen",
3220+ "wasm-bindgen-futures",
3221+ "web-sys",
3222+ "webpki-roots",
3223+]
3224+
3225+[[package]]
3226+name = "reqwest"
3227+version = "0.13.5"
3228+source = "registry+https://github.com/rust-lang/crates.io-index"
3229+checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
3230+dependencies = [
3231+ "base64 0.23.1",
3232+ "bytes",
3233+ "futures-core",
3234+ "futures-util",
3235+ "http",
3236+ "http-body",
3237+ "http-body-util",
3238+ "hyper",
3239+ "hyper-rustls",
3240+ "hyper-util",
3241+ "js-sys",
3242+ "log",
3243+ "percent-encoding",
3244+ "pin-project-lite",
3245+ "rustls",
3246+ "rustls-pki-types",
3247+ "rustls-platform-verifier",
3248+ "serde",
3249+ "serde_json",
3250+ "sync_wrapper",
3251+ "tokio",
3252+ "tokio-rustls",
3253+ "tokio-util",
3254+ "tower",
3255+ "tower-http",
3256+ "tower-service",
3257+ "url",
3258+ "wasm-bindgen",
3259+ "wasm-bindgen-futures",
3260+ "wasm-streams",
3261+ "web-sys",
3262+]
3263+
3264+[[package]]
3265+name = "ring"
3266+version = "0.17.14"
3267+source = "registry+https://github.com/rust-lang/crates.io-index"
3268+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
3269+dependencies = [
3270+ "cc",
3271+ "cfg-if",
3272+ "getrandom 0.2.17",
3273+ "libc",
3274+ "untrusted",
3275+ "windows-sys 0.52.0",
3276+]
3277+
3278+[[package]]
3279+name = "rust-ini"
3280+version = "0.21.3"
3281+source = "registry+https://github.com/rust-lang/crates.io-index"
3282+checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
3283+dependencies = [
3284+ "cfg-if",
3285+ "ordered-multimap",
3286+]
3287+
3288+[[package]]
3289+name = "rustc-hash"
3290+version = "2.1.3"
3291+source = "registry+https://github.com/rust-lang/crates.io-index"
3292+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
3293+
3294+[[package]]
3295+name = "rustc_version"
3296+version = "0.4.1"
3297+source = "registry+https://github.com/rust-lang/crates.io-index"
3298+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
3299+dependencies = [
3300+ "semver",
3301+]
3302+
3303+[[package]]
3304+name = "rustix"
3305+version = "1.1.5"
3306+source = "registry+https://github.com/rust-lang/crates.io-index"
3307+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
3308+dependencies = [
3309+ "bitflags 2.13.2",
3310+ "errno",
3311+ "libc",
3312+ "linux-raw-sys",
3313+ "windows-sys 0.61.2",
3314+]
3315+
3316+[[package]]
3317+name = "rustls"
3318+version = "0.23.45"
3319+source = "registry+https://github.com/rust-lang/crates.io-index"
3320+checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
3321+dependencies = [
3322+ "once_cell",
3323+ "ring",
3324+ "rustls-pki-types",
3325+ "rustls-webpki",
3326+ "subtle",
3327+ "zeroize",
3328+]
3329+
3330+[[package]]
3331+name = "rustls-native-certs"
3332+version = "0.8.4"
3333+source = "registry+https://github.com/rust-lang/crates.io-index"
3334+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
3335+dependencies = [
3336+ "openssl-probe",
3337+ "rustls-pki-types",
3338+ "schannel",
3339+ "security-framework",
3340+]
3341+
3342+[[package]]
3343+name = "rustls-pki-types"
3344+version = "1.15.1"
3345+source = "registry+https://github.com/rust-lang/crates.io-index"
3346+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
3347+dependencies = [
3348+ "web-time",
3349+ "zeroize",
3350+]
3351+
3352+[[package]]
3353+name = "rustls-platform-verifier"
3354+version = "0.7.1"
3355+source = "registry+https://github.com/rust-lang/crates.io-index"
3356+checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98"
3357+dependencies = [
3358+ "core-foundation 0.10.1",
3359+ "core-foundation-sys",
3360+ "jni 0.22.4",
3361+ "log",
3362+ "once_cell",
3363+ "rustls",
3364+ "rustls-native-certs",
3365+ "rustls-platform-verifier-android",
3366+ "rustls-webpki",
3367+ "security-framework",
3368+ "security-framework-sys",
3369+ "webpki-root-certs",
3370+ "windows-sys 0.61.2",
3371+]
3372+
3373+[[package]]
3374+name = "rustls-platform-verifier-android"
3375+version = "0.2.0"
3376+source = "registry+https://github.com/rust-lang/crates.io-index"
3377+checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
3378+
3379+[[package]]
3380+name = "rustls-webpki"
3381+version = "0.103.15"
3382+source = "registry+https://github.com/rust-lang/crates.io-index"
3383+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
3384+dependencies = [
3385+ "ring",
3386+ "rustls-pki-types",
3387+ "untrusted",
3388+]
3389+
3390+[[package]]
3391+name = "rustversion"
3392+version = "1.0.23"
3393+source = "registry+https://github.com/rust-lang/crates.io-index"
3394+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
3395+
3396+[[package]]
3397+name = "ryu"
3398+version = "1.0.23"
3399+source = "registry+https://github.com/rust-lang/crates.io-index"
3400+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
3401+
3402+[[package]]
3403+name = "same-file"
3404+version = "1.0.6"
3405+source = "registry+https://github.com/rust-lang/crates.io-index"
3406+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
3407+dependencies = [
3408+ "winapi-util",
3409+]
3410+
3411+[[package]]
3412+name = "schannel"
3413+version = "0.1.29"
3414+source = "registry+https://github.com/rust-lang/crates.io-index"
3415+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
3416+dependencies = [
3417+ "windows-sys 0.61.2",
3418+]
3419+
3420+[[package]]
3421+name = "schemars"
3422+version = "0.8.22"
3423+source = "registry+https://github.com/rust-lang/crates.io-index"
3424+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
3425+dependencies = [
3426+ "dyn-clone",
3427+ "indexmap 1.9.3",
3428+ "schemars_derive",
3429+ "serde",
3430+ "serde_json",
3431+ "url",
3432+ "uuid",
3433+]
3434+
3435+[[package]]
3436+name = "schemars"
3437+version = "0.9.0"
3438+source = "registry+https://github.com/rust-lang/crates.io-index"
3439+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
3440+dependencies = [
3441+ "dyn-clone",
3442+ "ref-cast",
3443+ "serde",
3444+ "serde_json",
3445+]
3446+
3447+[[package]]
3448+name = "schemars"
3449+version = "1.2.2"
3450+source = "registry+https://github.com/rust-lang/crates.io-index"
3451+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
3452+dependencies = [
3453+ "dyn-clone",
3454+ "ref-cast",
3455+ "serde",
3456+ "serde_json",
3457+]
3458+
3459+[[package]]
3460+name = "schemars_derive"
3461+version = "0.8.22"
3462+source = "registry+https://github.com/rust-lang/crates.io-index"
3463+checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
3464+dependencies = [
3465+ "proc-macro2",
3466+ "quote",
3467+ "serde_derive_internals",
3468+ "syn 2.0.119",
3469+]
3470+
3471+[[package]]
3472+name = "scopeguard"
3473+version = "1.2.0"
3474+source = "registry+https://github.com/rust-lang/crates.io-index"
3475+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
3476+
3477+[[package]]
3478+name = "security-framework"
3479+version = "3.7.0"
3480+source = "registry+https://github.com/rust-lang/crates.io-index"
3481+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
3482+dependencies = [
3483+ "bitflags 2.13.2",
3484+ "core-foundation 0.10.1",
3485+ "core-foundation-sys",
3486+ "libc",
3487+ "security-framework-sys",
3488+]
3489+
3490+[[package]]
3491+name = "security-framework-sys"
3492+version = "2.17.0"
3493+source = "registry+https://github.com/rust-lang/crates.io-index"
3494+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
3495+dependencies = [
3496+ "core-foundation-sys",
3497+ "libc",
3498+]
3499+
3500+[[package]]
3501+name = "selectors"
3502+version = "0.38.0"
3503+source = "registry+https://github.com/rust-lang/crates.io-index"
3504+checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d"
3505+dependencies = [
3506+ "bitflags 2.13.2",
3507+ "cssparser",
3508+ "derive_more",
3509+ "log",
3510+ "new_debug_unreachable",
3511+ "phf",
3512+ "phf_codegen",
3513+ "precomputed-hash",
3514+ "rustc-hash",
3515+ "servo_arc",
3516+ "smallvec",
3517+]
3518+
3519+[[package]]
3520+name = "semver"
3521+version = "1.0.28"
3522+source = "registry+https://github.com/rust-lang/crates.io-index"
3523+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
3524+dependencies = [
3525+ "serde",
3526+ "serde_core",
3527+]
3528+
3529+[[package]]
3530+name = "serde"
3531+version = "1.0.229"
3532+source = "registry+https://github.com/rust-lang/crates.io-index"
3533+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
3534+dependencies = [
3535+ "serde_core",
3536+ "serde_derive",
3537+]
3538+
3539+[[package]]
3540+name = "serde-untagged"
3541+version = "0.1.9"
3542+source = "registry+https://github.com/rust-lang/crates.io-index"
3543+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
3544+dependencies = [
3545+ "erased-serde",
3546+ "serde",
3547+ "serde_core",
3548+ "typeid",
3549+]
3550+
3551+[[package]]
3552+name = "serde_core"
3553+version = "1.0.229"
3554+source = "registry+https://github.com/rust-lang/crates.io-index"
3555+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
3556+dependencies = [
3557+ "serde_derive",
3558+]
3559+
3560+[[package]]
3561+name = "serde_derive"
3562+version = "1.0.229"
3563+source = "registry+https://github.com/rust-lang/crates.io-index"
3564+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
3565+dependencies = [
3566+ "proc-macro2",
3567+ "quote",
3568+ "syn 3.0.7",
3569+]
3570+
3571+[[package]]
3572+name = "serde_derive_internals"
3573+version = "0.29.1"
3574+source = "registry+https://github.com/rust-lang/crates.io-index"
3575+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
3576+dependencies = [
3577+ "proc-macro2",
3578+ "quote",
3579+ "syn 2.0.119",
3580+]
3581+
3582+[[package]]
3583+name = "serde_json"
3584+version = "1.0.152"
3585+source = "registry+https://github.com/rust-lang/crates.io-index"
3586+checksum = "1741ab7a6cc54a03a89b5d563ed60075c277d9e3cfa73ad0c1f23f23974703c6"
3587+dependencies = [
3588+ "itoa",
3589+ "memchr",
3590+ "serde",
3591+ "serde_core",
3592+ "zmij",
3593+]
3594+
3595+[[package]]
3596+name = "serde_repr"
3597+version = "0.1.21"
3598+source = "registry+https://github.com/rust-lang/crates.io-index"
3599+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
3600+dependencies = [
3601+ "proc-macro2",
3602+ "quote",
3603+ "syn 3.0.7",
3604+]
3605+
3606+[[package]]
3607+name = "serde_spanned"
3608+version = "0.6.9"
3609+source = "registry+https://github.com/rust-lang/crates.io-index"
3610+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
3611+dependencies = [
3612+ "serde",
3613+]
3614+
3615+[[package]]
3616+name = "serde_spanned"
3617+version = "1.1.2"
3618+source = "registry+https://github.com/rust-lang/crates.io-index"
3619+checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
3620+dependencies = [
3621+ "serde_core",
3622+]
3623+
3624+[[package]]
3625+name = "serde_urlencoded"
3626+version = "0.7.1"
3627+source = "registry+https://github.com/rust-lang/crates.io-index"
3628+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
3629+dependencies = [
3630+ "form_urlencoded",
3631+ "itoa",
3632+ "ryu",
3633+ "serde",
3634+]
3635+
3636+[[package]]
3637+name = "serde_with"
3638+version = "3.24.0"
3639+source = "registry+https://github.com/rust-lang/crates.io-index"
3640+checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f"
3641+dependencies = [
3642+ "base64 0.23.1",
3643+ "bs58",
3644+ "chrono",
3645+ "hex",
3646+ "indexmap 1.9.3",
3647+ "indexmap 2.14.2",
3648+ "jiff",
3649+ "schemars 0.9.0",
3650+ "schemars 1.2.2",
3651+ "serde_core",
3652+ "serde_json",
3653+ "serde_with_macros",
3654+ "time",
3655+]
3656+
3657+[[package]]
3658+name = "serde_with_macros"
3659+version = "3.24.0"
3660+source = "registry+https://github.com/rust-lang/crates.io-index"
3661+checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49"
3662+dependencies = [
3663+ "darling",
3664+ "proc-macro2",
3665+ "quote",
3666+ "syn 3.0.7",
3667+]
3668+
3669+[[package]]
3670+name = "serialize-to-javascript"
3671+version = "0.1.2"
3672+source = "registry+https://github.com/rust-lang/crates.io-index"
3673+checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
3674+dependencies = [
3675+ "serde",
3676+ "serde_json",
3677+ "serialize-to-javascript-impl",
3678+]
3679+
3680+[[package]]
3681+name = "serialize-to-javascript-impl"
3682+version = "0.1.2"
3683+source = "registry+https://github.com/rust-lang/crates.io-index"
3684+checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
3685+dependencies = [
3686+ "proc-macro2",
3687+ "quote",
3688+ "syn 2.0.119",
3689+]
3690+
3691+[[package]]
3692+name = "servo_arc"
3693+version = "0.4.3"
3694+source = "registry+https://github.com/rust-lang/crates.io-index"
3695+checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930"
3696+dependencies = [
3697+ "stable_deref_trait",
3698+]
3699+
3700+[[package]]
3701+name = "sha2"
3702+version = "0.10.9"
3703+source = "registry+https://github.com/rust-lang/crates.io-index"
3704+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
3705+dependencies = [
3706+ "cfg-if",
3707+ "cpufeatures 0.2.17",
3708+ "digest",
3709+]
3710+
3711+[[package]]
3712+name = "shlex"
3713+version = "2.0.1"
3714+source = "registry+https://github.com/rust-lang/crates.io-index"
3715+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
3716+
3717+[[package]]
3718+name = "signal-hook-registry"
3719+version = "1.4.8"
3720+source = "registry+https://github.com/rust-lang/crates.io-index"
3721+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
3722+dependencies = [
3723+ "errno",
3724+ "libc",
3725+]
3726+
3727+[[package]]
3728+name = "simd-adler32"
3729+version = "0.3.10"
3730+source = "registry+https://github.com/rust-lang/crates.io-index"
3731+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
3732+
3733+[[package]]
3734+name = "simd_cesu8"
3735+version = "1.2.0"
3736+source = "registry+https://github.com/rust-lang/crates.io-index"
3737+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
3738+dependencies = [
3739+ "rustc_version",
3740+ "simdutf8",
3741+]
3742+
3743+[[package]]
3744+name = "simdutf8"
3745+version = "0.1.5"
3746+source = "registry+https://github.com/rust-lang/crates.io-index"
3747+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
3748+
3749+[[package]]
3750+name = "siphasher"
3751+version = "1.0.4"
3752+source = "registry+https://github.com/rust-lang/crates.io-index"
3753+checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
3754+
3755+[[package]]
3756+name = "slab"
3757+version = "0.4.12"
3758+source = "registry+https://github.com/rust-lang/crates.io-index"
3759+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
3760+
3761+[[package]]
3762+name = "smallvec"
3763+version = "1.16.3"
3764+source = "registry+https://github.com/rust-lang/crates.io-index"
3765+checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
3766+
3767+[[package]]
3768+name = "socket2"
3769+version = "0.6.5"
3770+source = "registry+https://github.com/rust-lang/crates.io-index"
3771+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
3772+dependencies = [
3773+ "libc",
3774+ "windows-sys 0.61.2",
3775+]
3776+
3777+[[package]]
3778+name = "softbuffer"
3779+version = "0.4.8"
3780+source = "registry+https://github.com/rust-lang/crates.io-index"
3781+checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
3782+dependencies = [
3783+ "bytemuck",
3784+ "js-sys",
3785+ "ndk",
3786+ "objc2",
3787+ "objc2-core-foundation",
3788+ "objc2-core-graphics",
3789+ "objc2-foundation",
3790+ "objc2-quartz-core",
3791+ "raw-window-handle",
3792+ "redox_syscall",
3793+ "tracing",
3794+ "wasm-bindgen",
3795+ "web-sys",
3796+ "windows-sys 0.61.2",
3797+]
3798+
3799+[[package]]
3800+name = "soup3"
3801+version = "0.5.0"
3802+source = "registry+https://github.com/rust-lang/crates.io-index"
3803+checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
3804+dependencies = [
3805+ "futures-channel",
3806+ "gio",
3807+ "glib",
3808+ "libc",
3809+ "soup3-sys",
3810+]
3811+
3812+[[package]]
3813+name = "soup3-sys"
3814+version = "0.5.0"
3815+source = "registry+https://github.com/rust-lang/crates.io-index"
3816+checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27"
3817+dependencies = [
3818+ "gio-sys",
3819+ "glib-sys",
3820+ "gobject-sys",
3821+ "libc",
3822+ "system-deps",
3823+]
3824+
3825+[[package]]
3826+name = "stable_deref_trait"
3827+version = "1.2.1"
3828+source = "registry+https://github.com/rust-lang/crates.io-index"
3829+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3830+
3831+[[package]]
3832+name = "string_cache"
3833+version = "0.9.0"
3834+source = "registry+https://github.com/rust-lang/crates.io-index"
3835+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
3836+dependencies = [
3837+ "new_debug_unreachable",
3838+ "parking_lot",
3839+ "phf_shared",
3840+ "precomputed-hash",
3841+]
3842+
3843+[[package]]
3844+name = "string_cache_codegen"
3845+version = "0.6.1"
3846+source = "registry+https://github.com/rust-lang/crates.io-index"
3847+checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
3848+dependencies = [
3849+ "phf_generator",
3850+ "phf_shared",
3851+ "proc-macro2",
3852+ "quote",
3853+]
3854+
3855+[[package]]
3856+name = "strsim"
3857+version = "0.11.1"
3858+source = "registry+https://github.com/rust-lang/crates.io-index"
3859+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
3860+
3861+[[package]]
3862+name = "subtle"
3863+version = "2.6.1"
3864+source = "registry+https://github.com/rust-lang/crates.io-index"
3865+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
3866+
3867+[[package]]
3868+name = "swift-rs"
3869+version = "1.0.9"
3870+source = "registry+https://github.com/rust-lang/crates.io-index"
3871+checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9"
3872+dependencies = [
3873+ "base64 0.21.7",
3874+ "serde",
3875+ "serde_json",
3876+]
3877+
3878+[[package]]
3879+name = "syn"
3880+version = "1.0.109"
3881+source = "registry+https://github.com/rust-lang/crates.io-index"
3882+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
3883+dependencies = [
3884+ "proc-macro2",
3885+ "unicode-ident",
3886+]
3887+
3888+[[package]]
3889+name = "syn"
3890+version = "2.0.119"
3891+source = "registry+https://github.com/rust-lang/crates.io-index"
3892+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
3893+dependencies = [
3894+ "proc-macro2",
3895+ "quote",
3896+ "unicode-ident",
3897+]
3898+
3899+[[package]]
3900+name = "syn"
3901+version = "3.0.7"
3902+source = "registry+https://github.com/rust-lang/crates.io-index"
3903+checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc"
3904+dependencies = [
3905+ "proc-macro2",
3906+ "quote",
3907+ "unicode-ident",
3908+]
3909+
3910+[[package]]
3911+name = "sync_wrapper"
3912+version = "1.0.2"
3913+source = "registry+https://github.com/rust-lang/crates.io-index"
3914+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3915+dependencies = [
3916+ "futures-core",
3917+]
3918+
3919+[[package]]
3920+name = "synstructure"
3921+version = "0.14.0"
3922+source = "registry+https://github.com/rust-lang/crates.io-index"
3923+checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
3924+dependencies = [
3925+ "proc-macro2",
3926+ "quote",
3927+ "syn 3.0.7",
3928+]
3929+
3930+[[package]]
3931+name = "system-configuration"
3932+version = "0.7.0"
3933+source = "registry+https://github.com/rust-lang/crates.io-index"
3934+checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
3935+dependencies = [
3936+ "bitflags 2.13.2",
3937+ "core-foundation 0.9.4",
3938+ "system-configuration-sys",
3939+]
3940+
3941+[[package]]
3942+name = "system-configuration-sys"
3943+version = "0.6.0"
3944+source = "registry+https://github.com/rust-lang/crates.io-index"
3945+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
3946+dependencies = [
3947+ "core-foundation-sys",
3948+ "libc",
3949+]
3950+
3951+[[package]]
3952+name = "system-deps"
3953+version = "6.2.2"
3954+source = "registry+https://github.com/rust-lang/crates.io-index"
3955+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
3956+dependencies = [
3957+ "cfg-expr",
3958+ "heck 0.5.0",
3959+ "pkg-config",
3960+ "toml 0.8.2",
3961+ "version-compare",
3962+]
3963+
3964+[[package]]
3965+name = "tao"
3966+version = "0.37.1"
3967+source = "registry+https://github.com/rust-lang/crates.io-index"
3968+checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400"
3969+dependencies = [
3970+ "bitflags 2.13.2",
3971+ "block2",
3972+ "core-foundation 0.10.1",
3973+ "core-graphics",
3974+ "crossbeam-channel",
3975+ "dbus",
3976+ "dispatch2",
3977+ "dlopen2",
3978+ "dpi",
3979+ "gdkwayland-sys",
3980+ "gdkx11-sys",
3981+ "gtk",
3982+ "jni 0.21.1",
3983+ "libc",
3984+ "log",
3985+ "ndk",
3986+ "ndk-context",
3987+ "ndk-sys",
3988+ "objc2",
3989+ "objc2-app-kit",
3990+ "objc2-foundation",
3991+ "objc2-ui-kit",
3992+ "parking_lot",
3993+ "percent-encoding",
3994+ "raw-window-handle",
3995+ "tao-macros",
3996+ "unicode-segmentation",
3997+ "url",
3998+ "windows",
3999+ "windows-core",
4000+ "windows-version",
4001+ "x11-dl",
4002+]
4003+
4004+[[package]]
4005+name = "tao-macros"
4006+version = "0.1.4"
4007+source = "registry+https://github.com/rust-lang/crates.io-index"
4008+checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849"
4009+dependencies = [
4010+ "proc-macro2",
4011+ "quote",
4012+ "syn 2.0.119",
4013+]
4014+
4015+[[package]]
4016+name = "tar"
4017+version = "0.4.46"
4018+source = "registry+https://github.com/rust-lang/crates.io-index"
4019+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
4020+dependencies = [
4021+ "filetime",
4022+ "libc",
4023+ "xattr",
4024+]
4025+
4026+[[package]]
4027+name = "target-lexicon"
4028+version = "0.12.16"
4029+source = "registry+https://github.com/rust-lang/crates.io-index"
4030+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
4031+
4032+[[package]]
4033+name = "tauri"
4034+version = "2.12.3"
4035+source = "registry+https://github.com/rust-lang/crates.io-index"
4036+checksum = "c404ecf956ff241bf6b39dd688614724e1bf1f63255abb4fc46d1c40371672ea"
4037+dependencies = [
4038+ "anyhow",
4039+ "bytes",
4040+ "cookie",
4041+ "dirs",
4042+ "dunce",
4043+ "embed_plist",
4044+ "getrandom 0.3.4",
4045+ "glob",
4046+ "gtk",
4047+ "heck 0.5.0",
4048+ "http",
4049+ "image",
4050+ "jni 0.21.1",
4051+ "libc",
4052+ "log",
4053+ "mime",
4054+ "muda",
4055+ "objc2",
4056+ "objc2-app-kit",
4057+ "objc2-foundation",
4058+ "objc2-ui-kit",
4059+ "objc2-web-kit",
4060+ "percent-encoding",
4061+ "plist",
4062+ "raw-window-handle",
4063+ "reqwest 0.13.5",
4064+ "serde",
4065+ "serde_json",
4066+ "serde_repr",
4067+ "serialize-to-javascript",
4068+ "swift-rs",
4069+ "tauri-build",
4070+ "tauri-macros",
4071+ "tauri-runtime",
4072+ "tauri-runtime-wry",
4073+ "tauri-utils",
4074+ "thiserror 2.0.21",
4075+ "tokio",
4076+ "tray-icon",
4077+ "url",
4078+ "webkit2gtk",
4079+ "webview2-com",
4080+ "window-vibrancy",
4081+ "windows",
4082+]
4083+
4084+[[package]]
4085+name = "tauri-build"
4086+version = "2.7.2"
4087+source = "registry+https://github.com/rust-lang/crates.io-index"
4088+checksum = "2eea0b6302f82c574b21af6fc6ae6e86183b9280853c22621d0abf0936911ffc"
4089+dependencies = [
4090+ "anyhow",
4091+ "cargo_toml",
4092+ "dirs",
4093+ "glob",
4094+ "heck 0.5.0",
4095+ "json-patch",
4096+ "schemars 0.8.22",
4097+ "semver",
4098+ "serde",
4099+ "serde_json",
4100+ "tauri-utils",
4101+ "tauri-winres",
4102+ "walkdir",
4103+]
4104+
4105+[[package]]
4106+name = "tauri-codegen"
4107+version = "2.7.2"
4108+source = "registry+https://github.com/rust-lang/crates.io-index"
4109+checksum = "02f468f9a9bcd404f57e5c191d91024e3de953d4b610f78c2907e7110615cb1c"
4110+dependencies = [
4111+ "base64 0.23.1",
4112+ "brotli",
4113+ "ico",
4114+ "json-patch",
4115+ "plist",
4116+ "png 0.18.1",
4117+ "proc-macro2",
4118+ "quote",
4119+ "semver",
4120+ "serde",
4121+ "serde_json",
4122+ "sha2",
4123+ "syn 2.0.119",
4124+ "tauri-utils",
4125+ "thiserror 2.0.21",
4126+ "time",
4127+ "url",
4128+ "uuid",
4129+ "walkdir",
4130+]
4131+
4132+[[package]]
4133+name = "tauri-macros"
4134+version = "2.7.2"
4135+source = "registry+https://github.com/rust-lang/crates.io-index"
4136+checksum = "131bffec939b20642380317f8403a33c8888489925e14bb965ceaff0d949b1cb"
4137+dependencies = [
4138+ "heck 0.5.0",
4139+ "proc-macro2",
4140+ "quote",
4141+ "syn 2.0.119",
4142+ "tauri-codegen",
4143+ "tauri-utils",
4144+]
4145+
4146+[[package]]
4147+name = "tauri-plugin"
4148+version = "2.7.1"
4149+source = "registry+https://github.com/rust-lang/crates.io-index"
4150+checksum = "1140cf34a3b3b836a13103dcab17f18831d5cc3534cbd435dc01a5c6daa65aa2"
4151+dependencies = [
4152+ "anyhow",
4153+ "glob",
4154+ "plist",
4155+ "schemars 0.8.22",
4156+ "serde",
4157+ "serde_json",
4158+ "tauri-utils",
4159+ "walkdir",
4160+]
4161+
4162+[[package]]
4163+name = "tauri-plugin-deep-link"
4164+version = "2.6.1"
4165+source = "registry+https://github.com/rust-lang/crates.io-index"
4166+checksum = "4ac7a92a46ab5c88f44532ca50906d6e448a948d4ddf8c5376ab125e3260f736"
4167+dependencies = [
4168+ "dunce",
4169+ "plist",
4170+ "rust-ini",
4171+ "serde",
4172+ "serde_json",
4173+ "tauri",
4174+ "tauri-plugin",
4175+ "tauri-utils",
4176+ "thiserror 2.0.21",
4177+ "tracing",
4178+ "url",
4179+ "windows-registry",
4180+ "windows-result",
4181+]
4182+
4183+[[package]]
4184+name = "tauri-plugin-notification"
4185+version = "2.6.0"
4186+source = "registry+https://github.com/rust-lang/crates.io-index"
4187+checksum = "8cf3cce3ea492b3a8a7f431a4e5dd5d31dc41b2a4a243faa660cbe8095a577bd"
4188+dependencies = [
4189+ "log",
4190+ "notify-rust",
4191+ "rand 0.9.5",
4192+ "serde",
4193+ "serde_json",
4194+ "serde_repr",
4195+ "tauri",
4196+ "tauri-plugin",
4197+ "thiserror 2.0.21",
4198+ "time",
4199+ "url",
4200+ "zbus",
4201+]
4202+
4203+[[package]]
4204+name = "tauri-plugin-opener"
4205+version = "2.7.1"
4206+source = "registry+https://github.com/rust-lang/crates.io-index"
4207+checksum = "85dead674ee41937d042ee855d785f07ee67206f19402200c12b9255964d5ac8"
4208+dependencies = [
4209+ "dunce",
4210+ "glob",
4211+ "objc2-app-kit",
4212+ "objc2-foundation",
4213+ "open",
4214+ "schemars 0.8.22",
4215+ "serde",
4216+ "serde_json",
4217+ "tauri",
4218+ "tauri-plugin",
4219+ "thiserror 2.0.21",
4220+ "url",
4221+ "windows",
4222+ "zbus",
4223+]
4224+
4225+[[package]]
4226+name = "tauri-plugin-single-instance"
4227+version = "2.5.3"
4228+source = "registry+https://github.com/rust-lang/crates.io-index"
4229+checksum = "3c142ed88deee222bd2d979269d35c73b6c1c0f6ebd5b79b4ff80066fcad6af1"
4230+dependencies = [
4231+ "serde",
4232+ "serde_json",
4233+ "tauri",
4234+ "tauri-plugin-deep-link",
4235+ "thiserror 2.0.21",
4236+ "tokio",
4237+ "tracing",
4238+ "windows-sys 0.61.2",
4239+ "zbus",
4240+]
4241+
4242+[[package]]
4243+name = "tauri-plugin-updater"
4244+version = "2.13.3"
4245+source = "registry+https://github.com/rust-lang/crates.io-index"
4246+checksum = "855a336eb389d2c1502244fd48fa2d8cb487a430f691bbaed025195f99a04489"
4247+dependencies = [
4248+ "base64 0.22.1",
4249+ "dirs",
4250+ "flate2",
4251+ "futures-util",
4252+ "http",
4253+ "infer",
4254+ "libc",
4255+ "log",
4256+ "minisign-verify",
4257+ "osakit",
4258+ "percent-encoding",
4259+ "reqwest 0.13.5",
4260+ "rustls",
4261+ "semver",
4262+ "serde",
4263+ "serde_json",
4264+ "tar",
4265+ "tauri",
4266+ "tauri-plugin",
4267+ "tempfile",
4268+ "thiserror 2.0.21",
4269+ "time",
4270+ "tokio",
4271+ "url",
4272+ "windows-sys 0.61.2",
4273+ "zip",
4274+]
4275+
4276+[[package]]
4277+name = "tauri-plugin-window-state"
4278+version = "2.5.0"
4279+source = "registry+https://github.com/rust-lang/crates.io-index"
4280+checksum = "5d6815cab7b212f9cfe16a9d79d8d22f9e14159e4a9460f7dec1467167a008e3"
4281+dependencies = [
4282+ "bitflags 2.13.2",
4283+ "log",
4284+ "serde",
4285+ "serde_json",
4286+ "tauri",
4287+ "tauri-plugin",
4288+ "thiserror 2.0.21",
4289+]
4290+
4291+[[package]]
4292+name = "tauri-runtime"
4293+version = "2.12.1"
4294+source = "registry+https://github.com/rust-lang/crates.io-index"
4295+checksum = "c2a57a2a9b8c8b7fc00d35b510fe808ded7f9f1b44fc1cdda6aee38632ea5413"
4296+dependencies = [
4297+ "cookie",
4298+ "dpi",
4299+ "gtk",
4300+ "http",
4301+ "jni 0.21.1",
4302+ "objc2",
4303+ "objc2-ui-kit",
4304+ "objc2-web-kit",
4305+ "raw-window-handle",
4306+ "serde",
4307+ "serde_json",
4308+ "tauri-utils",
4309+ "thiserror 2.0.21",
4310+ "url",
4311+ "webkit2gtk",
4312+ "webview2-com",
4313+ "windows",
4314+]
4315+
4316+[[package]]
4317+name = "tauri-runtime-wry"
4318+version = "2.12.1"
4319+source = "registry+https://github.com/rust-lang/crates.io-index"
4320+checksum = "7f9e7e0c9f80a5130773cfabb6df4dc924518fb1b2c923726966804634b57d22"
4321+dependencies = [
4322+ "gtk",
4323+ "http",
4324+ "jni 0.21.1",
4325+ "log",
4326+ "objc2",
4327+ "objc2-app-kit",
4328+ "once_cell",
4329+ "percent-encoding",
4330+ "raw-window-handle",
4331+ "softbuffer",
4332+ "tao",
4333+ "tauri-runtime",
4334+ "tauri-utils",
4335+ "url",
4336+ "webkit2gtk",
4337+ "webview2-com",
4338+ "windows",
4339+ "wry",
4340+]
4341+
4342+[[package]]
4343+name = "tauri-utils"
4344+version = "2.10.1"
4345+source = "registry+https://github.com/rust-lang/crates.io-index"
4346+checksum = "2ff55a614843b9a3f010f211df637c94df2607175b8d0f6e0f4a7c19e18621ee"
4347+dependencies = [
4348+ "anyhow",
4349+ "brotli",
4350+ "cargo_metadata",
4351+ "ctor",
4352+ "dom_query",
4353+ "dunce",
4354+ "glob",
4355+ "http",
4356+ "infer",
4357+ "json-patch",
4358+ "log",
4359+ "memchr",
4360+ "phf",
4361+ "plist",
4362+ "proc-macro2",
4363+ "quote",
4364+ "regex",
4365+ "schemars 0.8.22",
4366+ "semver",
4367+ "serde",
4368+ "serde-untagged",
4369+ "serde_json",
4370+ "serde_with",
4371+ "swift-rs",
4372+ "thiserror 2.0.21",
4373+ "toml 1.1.8+spec-1.1.0",
4374+ "url",
4375+ "urlpattern",
4376+ "uuid",
4377+ "walkdir",
4378+]
4379+
4380+[[package]]
4381+name = "tauri-winres"
4382+version = "0.3.6"
4383+source = "registry+https://github.com/rust-lang/crates.io-index"
4384+checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6"
4385+dependencies = [
4386+ "dunce",
4387+ "embed-resource",
4388+ "toml 1.1.8+spec-1.1.0",
4389+]
4390+
4391+[[package]]
4392+name = "tauri-winrt-notification"
4393+version = "0.8.1"
4394+source = "registry+https://github.com/rust-lang/crates.io-index"
4395+checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36"
4396+dependencies = [
4397+ "thiserror 2.0.21",
4398+ "windows",
4399+ "windows-version",
4400+]
4401+
4402+[[package]]
4403+name = "tempfile"
4404+version = "3.27.0"
4405+source = "registry+https://github.com/rust-lang/crates.io-index"
4406+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
4407+dependencies = [
4408+ "fastrand",
4409+ "getrandom 0.4.3",
4410+ "once_cell",
4411+ "rustix",
4412+ "windows-sys 0.61.2",
4413+]
4414+
4415+[[package]]
4416+name = "tendril"
4417+version = "0.5.1"
4418+source = "registry+https://github.com/rust-lang/crates.io-index"
4419+checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
4420+dependencies = [
4421+ "new_debug_unreachable",
4422+]
4423+
4424+[[package]]
4425+name = "thiserror"
4426+version = "1.0.69"
4427+source = "registry+https://github.com/rust-lang/crates.io-index"
4428+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
4429+dependencies = [
4430+ "thiserror-impl 1.0.69",
4431+]
4432+
4433+[[package]]
4434+name = "thiserror"
4435+version = "2.0.21"
4436+source = "registry+https://github.com/rust-lang/crates.io-index"
4437+checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
4438+dependencies = [
4439+ "thiserror-impl 2.0.21",
4440+]
4441+
4442+[[package]]
4443+name = "thiserror-impl"
4444+version = "1.0.69"
4445+source = "registry+https://github.com/rust-lang/crates.io-index"
4446+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
4447+dependencies = [
4448+ "proc-macro2",
4449+ "quote",
4450+ "syn 2.0.119",
4451+]
4452+
4453+[[package]]
4454+name = "thiserror-impl"
4455+version = "2.0.21"
4456+source = "registry+https://github.com/rust-lang/crates.io-index"
4457+checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
4458+dependencies = [
4459+ "proc-macro2",
4460+ "quote",
4461+ "syn 3.0.7",
4462+]
4463+
4464+[[package]]
4465+name = "time"
4466+version = "0.3.55"
4467+source = "registry+https://github.com/rust-lang/crates.io-index"
4468+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
4469+dependencies = [
4470+ "deranged",
4471+ "num-conv",
4472+ "powerfmt",
4473+ "serde_core",
4474+ "time-core",
4475+ "time-macros",
4476+]
4477+
4478+[[package]]
4479+name = "time-core"
4480+version = "0.1.9"
4481+source = "registry+https://github.com/rust-lang/crates.io-index"
4482+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
4483+
4484+[[package]]
4485+name = "time-macros"
4486+version = "0.2.32"
4487+source = "registry+https://github.com/rust-lang/crates.io-index"
4488+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
4489+dependencies = [
4490+ "num-conv",
4491+ "time-core",
4492+]
4493+
4494+[[package]]
4495+name = "tiny-keccak"
4496+version = "2.0.2"
4497+source = "registry+https://github.com/rust-lang/crates.io-index"
4498+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
4499+dependencies = [
4500+ "crunchy",
4501+]
4502+
4503+[[package]]
4504+name = "tinystr"
4505+version = "0.8.4"
4506+source = "registry+https://github.com/rust-lang/crates.io-index"
4507+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
4508+dependencies = [
4509+ "displaydoc",
4510+ "zerovec",
4511+]
4512+
4513+[[package]]
4514+name = "tinyvec"
4515+version = "1.13.3"
4516+source = "registry+https://github.com/rust-lang/crates.io-index"
4517+checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
4518+
4519+[[package]]
4520+name = "tokio"
4521+version = "1.53.2"
4522+source = "registry+https://github.com/rust-lang/crates.io-index"
4523+checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
4524+dependencies = [
4525+ "bytes",
4526+ "libc",
4527+ "mio",
4528+ "pin-project-lite",
4529+ "socket2",
4530+ "windows-sys 0.61.2",
4531+]
4532+
4533+[[package]]
4534+name = "tokio-rustls"
4535+version = "0.26.6"
4536+source = "registry+https://github.com/rust-lang/crates.io-index"
4537+checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"
4538+dependencies = [
4539+ "rustls",
4540+ "tokio",
4541+]
4542+
4543+[[package]]
4544+name = "tokio-util"
4545+version = "0.7.20"
4546+source = "registry+https://github.com/rust-lang/crates.io-index"
4547+checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b"
4548+dependencies = [
4549+ "bytes",
4550+ "futures-core",
4551+ "futures-sink",
4552+ "pin-project-lite",
4553+ "tokio",
4554+]
4555+
4556+[[package]]
4557+name = "toml"
4558+version = "0.8.2"
4559+source = "registry+https://github.com/rust-lang/crates.io-index"
4560+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
4561+dependencies = [
4562+ "serde",
4563+ "serde_spanned 0.6.9",
4564+ "toml_datetime 0.6.3",
4565+ "toml_edit 0.20.2",
4566+]
4567+
4568+[[package]]
4569+name = "toml"
4570+version = "1.1.8+spec-1.1.0"
4571+source = "registry+https://github.com/rust-lang/crates.io-index"
4572+checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d"
4573+dependencies = [
4574+ "indexmap 2.14.2",
4575+ "serde_core",
4576+ "serde_spanned 1.1.2",
4577+ "toml_datetime 1.1.2+spec-1.1.0",
4578+ "toml_parser",
4579+ "toml_writer",
4580+ "winnow 1.0.4",
4581+]
4582+
4583+[[package]]
4584+name = "toml_datetime"
4585+version = "0.6.3"
4586+source = "registry+https://github.com/rust-lang/crates.io-index"
4587+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
4588+dependencies = [
4589+ "serde",
4590+]
4591+
4592+[[package]]
4593+name = "toml_datetime"
4594+version = "1.1.2+spec-1.1.0"
4595+source = "registry+https://github.com/rust-lang/crates.io-index"
4596+checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
4597+dependencies = [
4598+ "serde_core",
4599+]
4600+
4601+[[package]]
4602+name = "toml_edit"
4603+version = "0.19.15"
4604+source = "registry+https://github.com/rust-lang/crates.io-index"
4605+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
4606+dependencies = [
4607+ "indexmap 2.14.2",
4608+ "toml_datetime 0.6.3",
4609+ "winnow 0.5.40",
4610+]
4611+
4612+[[package]]
4613+name = "toml_edit"
4614+version = "0.20.2"
4615+source = "registry+https://github.com/rust-lang/crates.io-index"
4616+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
4617+dependencies = [
4618+ "indexmap 2.14.2",
4619+ "serde",
4620+ "serde_spanned 0.6.9",
4621+ "toml_datetime 0.6.3",
4622+ "winnow 0.5.40",
4623+]
4624+
4625+[[package]]
4626+name = "toml_edit"
4627+version = "0.25.17+spec-1.1.0"
4628+source = "registry+https://github.com/rust-lang/crates.io-index"
4629+checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
4630+dependencies = [
4631+ "indexmap 2.14.2",
4632+ "toml_datetime 1.1.2+spec-1.1.0",
4633+ "toml_parser",
4634+ "winnow 1.0.4",
4635+]
4636+
4637+[[package]]
4638+name = "toml_parser"
4639+version = "1.1.5+spec-1.1.0"
4640+source = "registry+https://github.com/rust-lang/crates.io-index"
4641+checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
4642+dependencies = [
4643+ "winnow 1.0.4",
4644+]
4645+
4646+[[package]]
4647+name = "toml_writer"
4648+version = "1.1.3+spec-1.1.0"
4649+source = "registry+https://github.com/rust-lang/crates.io-index"
4650+checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770"
4651+
4652+[[package]]
4653+name = "tower"
4654+version = "0.5.3"
4655+source = "registry+https://github.com/rust-lang/crates.io-index"
4656+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
4657+dependencies = [
4658+ "futures-core",
4659+ "futures-util",
4660+ "pin-project-lite",
4661+ "sync_wrapper",
4662+ "tokio",
4663+ "tower-layer",
4664+ "tower-service",
4665+]
4666+
4667+[[package]]
4668+name = "tower-http"
4669+version = "0.6.11"
4670+source = "registry+https://github.com/rust-lang/crates.io-index"
4671+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
4672+dependencies = [
4673+ "bitflags 2.13.2",
4674+ "bytes",
4675+ "futures-util",
4676+ "http",
4677+ "http-body",
4678+ "pin-project-lite",
4679+ "tower",
4680+ "tower-layer",
4681+ "tower-service",
4682+ "url",
4683+]
4684+
4685+[[package]]
4686+name = "tower-layer"
4687+version = "0.3.3"
4688+source = "registry+https://github.com/rust-lang/crates.io-index"
4689+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
4690+
4691+[[package]]
4692+name = "tower-service"
4693+version = "0.3.3"
4694+source = "registry+https://github.com/rust-lang/crates.io-index"
4695+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
4696+
4697+[[package]]
4698+name = "tracing"
4699+version = "0.1.44"
4700+source = "registry+https://github.com/rust-lang/crates.io-index"
4701+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
4702+dependencies = [
4703+ "pin-project-lite",
4704+ "tracing-attributes",
4705+ "tracing-core",
4706+]
4707+
4708+[[package]]
4709+name = "tracing-attributes"
4710+version = "0.1.31"
4711+source = "registry+https://github.com/rust-lang/crates.io-index"
4712+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
4713+dependencies = [
4714+ "proc-macro2",
4715+ "quote",
4716+ "syn 2.0.119",
4717+]
4718+
4719+[[package]]
4720+name = "tracing-core"
4721+version = "0.1.36"
4722+source = "registry+https://github.com/rust-lang/crates.io-index"
4723+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
4724+dependencies = [
4725+ "once_cell",
4726+]
4727+
4728+[[package]]
4729+name = "tray-icon"
4730+version = "0.25.1"
4731+source = "registry+https://github.com/rust-lang/crates.io-index"
4732+checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72"
4733+dependencies = [
4734+ "crossbeam-channel",
4735+ "dirs",
4736+ "libappindicator",
4737+ "muda",
4738+ "objc2",
4739+ "objc2-app-kit",
4740+ "objc2-core-foundation",
4741+ "objc2-core-graphics",
4742+ "objc2-foundation",
4743+ "once_cell",
4744+ "png 0.18.1",
4745+ "serde",
4746+ "thiserror 2.0.21",
4747+ "windows-sys 0.61.2",
4748+]
4749+
4750+[[package]]
4751+name = "try-lock"
4752+version = "0.2.5"
4753+source = "registry+https://github.com/rust-lang/crates.io-index"
4754+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
4755+
4756+[[package]]
4757+name = "typeid"
4758+version = "1.0.3"
4759+source = "registry+https://github.com/rust-lang/crates.io-index"
4760+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
4761+
4762+[[package]]
4763+name = "typenum"
4764+version = "1.20.1"
4765+source = "registry+https://github.com/rust-lang/crates.io-index"
4766+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
4767+
4768+[[package]]
4769+name = "uds_windows"
4770+version = "1.2.1"
4771+source = "registry+https://github.com/rust-lang/crates.io-index"
4772+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
4773+dependencies = [
4774+ "memoffset",
4775+ "tempfile",
4776+ "windows-sys 0.61.2",
4777+]
4778+
4779+[[package]]
4780+name = "unicode-ident"
4781+version = "1.0.26"
4782+source = "registry+https://github.com/rust-lang/crates.io-index"
4783+checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
4784+
4785+[[package]]
4786+name = "unicode-segmentation"
4787+version = "1.13.3"
4788+source = "registry+https://github.com/rust-lang/crates.io-index"
4789+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
4790+
4791+[[package]]
4792+name = "untrusted"
4793+version = "0.9.0"
4794+source = "registry+https://github.com/rust-lang/crates.io-index"
4795+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
4796+
4797+[[package]]
4798+name = "url"
4799+version = "2.5.8"
4800+source = "registry+https://github.com/rust-lang/crates.io-index"
4801+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
4802+dependencies = [
4803+ "form_urlencoded",
4804+ "idna",
4805+ "percent-encoding",
4806+ "serde",
4807+ "serde_derive",
4808+]
4809+
4810+[[package]]
4811+name = "urlpattern"
4812+version = "0.6.0"
4813+source = "registry+https://github.com/rust-lang/crates.io-index"
4814+checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b"
4815+dependencies = [
4816+ "icu_properties",
4817+ "regex",
4818+ "serde",
4819+ "url",
4820+]
4821+
4822+[[package]]
4823+name = "utf8_iter"
4824+version = "1.0.4"
4825+source = "registry+https://github.com/rust-lang/crates.io-index"
4826+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
4827+
4828+[[package]]
4829+name = "uuid"
4830+version = "1.28.0"
4831+source = "registry+https://github.com/rust-lang/crates.io-index"
4832+checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606"
4833+dependencies = [
4834+ "getrandom 0.4.3",
4835+ "js-sys",
4836+ "serde_core",
4837+ "wasm-bindgen",
4838+]
4839+
4840+[[package]]
4841+name = "version-compare"
4842+version = "0.2.1"
4843+source = "registry+https://github.com/rust-lang/crates.io-index"
4844+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
4845+
4846+[[package]]
4847+name = "version_check"
4848+version = "0.9.5"
4849+source = "registry+https://github.com/rust-lang/crates.io-index"
4850+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
4851+
4852+[[package]]
4853+name = "vswhom"
4854+version = "0.1.0"
4855+source = "registry+https://github.com/rust-lang/crates.io-index"
4856+checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
4857+dependencies = [
4858+ "libc",
4859+ "vswhom-sys",
4860+]
4861+
4862+[[package]]
4863+name = "vswhom-sys"
4864+version = "0.1.3"
4865+source = "registry+https://github.com/rust-lang/crates.io-index"
4866+checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
4867+dependencies = [
4868+ "cc",
4869+ "libc",
4870+]
4871+
4872+[[package]]
4873+name = "walkdir"
4874+version = "2.5.0"
4875+source = "registry+https://github.com/rust-lang/crates.io-index"
4876+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
4877+dependencies = [
4878+ "same-file",
4879+ "winapi-util",
4880+]
4881+
4882+[[package]]
4883+name = "want"
4884+version = "0.3.2"
4885+source = "registry+https://github.com/rust-lang/crates.io-index"
4886+checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b"
4887+dependencies = [
4888+ "try-lock",
4889+]
4890+
4891+[[package]]
4892+name = "wasi"
4893+version = "0.11.1+wasi-snapshot-preview1"
4894+source = "registry+https://github.com/rust-lang/crates.io-index"
4895+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
4896+
4897+[[package]]
4898+name = "wasip2"
4899+version = "1.0.4+wasi-0.2.12"
4900+source = "registry+https://github.com/rust-lang/crates.io-index"
4901+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
4902+dependencies = [
4903+ "wit-bindgen",
4904+]
4905+
4906+[[package]]
4907+name = "wasm-bindgen"
4908+version = "0.2.129"
4909+source = "registry+https://github.com/rust-lang/crates.io-index"
4910+checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
4911+dependencies = [
4912+ "cfg-if",
4913+ "once_cell",
4914+ "rustversion",
4915+ "wasm-bindgen-macro",
4916+ "wasm-bindgen-shared",
4917+]
4918+
4919+[[package]]
4920+name = "wasm-bindgen-futures"
4921+version = "0.4.79"
4922+source = "registry+https://github.com/rust-lang/crates.io-index"
4923+checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
4924+dependencies = [
4925+ "js-sys",
4926+ "tokio",
4927+ "wasm-bindgen",
4928+]
4929+
4930+[[package]]
4931+name = "wasm-bindgen-macro"
4932+version = "0.2.129"
4933+source = "registry+https://github.com/rust-lang/crates.io-index"
4934+checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
4935+dependencies = [
4936+ "quote",
4937+ "wasm-bindgen-macro-support",
4938+]
4939+
4940+[[package]]
4941+name = "wasm-bindgen-macro-support"
4942+version = "0.2.129"
4943+source = "registry+https://github.com/rust-lang/crates.io-index"
4944+checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
4945+dependencies = [
4946+ "bumpalo",
4947+ "proc-macro2",
4948+ "quote",
4949+ "syn 3.0.7",
4950+ "wasm-bindgen-shared",
4951+]
4952+
4953+[[package]]
4954+name = "wasm-bindgen-shared"
4955+version = "0.2.129"
4956+source = "registry+https://github.com/rust-lang/crates.io-index"
4957+checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
4958+dependencies = [
4959+ "unicode-ident",
4960+]
4961+
4962+[[package]]
4963+name = "wasm-streams"
4964+version = "0.5.0"
4965+source = "registry+https://github.com/rust-lang/crates.io-index"
4966+checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
4967+dependencies = [
4968+ "futures-util",
4969+ "js-sys",
4970+ "wasm-bindgen",
4971+ "wasm-bindgen-futures",
4972+ "web-sys",
4973+]
4974+
4975+[[package]]
4976+name = "web-sys"
4977+version = "0.3.106"
4978+source = "registry+https://github.com/rust-lang/crates.io-index"
4979+checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
4980+dependencies = [
4981+ "js-sys",
4982+ "wasm-bindgen",
4983+]
4984+
4985+[[package]]
4986+name = "web-time"
4987+version = "1.1.0"
4988+source = "registry+https://github.com/rust-lang/crates.io-index"
4989+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
4990+dependencies = [
4991+ "js-sys",
4992+ "wasm-bindgen",
4993+]
4994+
4995+[[package]]
4996+name = "web_atoms"
4997+version = "0.2.6"
4998+source = "registry+https://github.com/rust-lang/crates.io-index"
4999+checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3"
5000+dependencies = [
5001+ "phf",
5002+ "phf_codegen",
5003+ "string_cache",
5004+ "string_cache_codegen",
5005+]
5006+
5007+[[package]]
5008+name = "webkit2gtk"
5009+version = "2.0.2"
5010+source = "registry+https://github.com/rust-lang/crates.io-index"
5011+checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
5012+dependencies = [
5013+ "bitflags 1.3.2",
5014+ "cairo-rs",
5015+ "gdk",
5016+ "gdk-sys",
5017+ "gio",
5018+ "gio-sys",
5019+ "glib",
5020+ "glib-sys",
5021+ "gobject-sys",
5022+ "gtk",
5023+ "gtk-sys",
5024+ "javascriptcore-rs",
5025+ "libc",
5026+ "once_cell",
5027+ "soup3",
5028+ "webkit2gtk-sys",
5029+]
5030+
5031+[[package]]
5032+name = "webkit2gtk-sys"
5033+version = "2.0.2"
5034+source = "registry+https://github.com/rust-lang/crates.io-index"
5035+checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
5036+dependencies = [
5037+ "bitflags 1.3.2",
5038+ "cairo-sys-rs",
5039+ "gdk-sys",
5040+ "gio-sys",
5041+ "glib-sys",
5042+ "gobject-sys",
5043+ "gtk-sys",
5044+ "javascriptcore-rs-sys",
5045+ "libc",
5046+ "pkg-config",
5047+ "soup3-sys",
5048+ "system-deps",
5049+]
5050+
5051+[[package]]
5052+name = "webpki-root-certs"
5053+version = "1.0.9"
5054+source = "registry+https://github.com/rust-lang/crates.io-index"
5055+checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
5056+dependencies = [
5057+ "rustls-pki-types",
5058+]
5059+
5060+[[package]]
5061+name = "webpki-roots"
5062+version = "1.0.9"
5063+source = "registry+https://github.com/rust-lang/crates.io-index"
5064+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
5065+dependencies = [
5066+ "rustls-pki-types",
5067+]
5068+
5069+[[package]]
5070+name = "webview2-com"
5071+version = "0.39.1"
5072+source = "registry+https://github.com/rust-lang/crates.io-index"
5073+checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6"
5074+dependencies = [
5075+ "webview2-com-macros",
5076+ "webview2-com-sys",
5077+ "windows",
5078+ "windows-core",
5079+]
5080+
5081+[[package]]
5082+name = "webview2-com-macros"
5083+version = "0.8.1"
5084+source = "registry+https://github.com/rust-lang/crates.io-index"
5085+checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
5086+dependencies = [
5087+ "proc-macro2",
5088+ "quote",
5089+ "syn 2.0.119",
5090+]
5091+
5092+[[package]]
5093+name = "webview2-com-sys"
5094+version = "0.39.1"
5095+source = "registry+https://github.com/rust-lang/crates.io-index"
5096+checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb"
5097+dependencies = [
5098+ "thiserror 2.0.21",
5099+ "windows",
5100+ "windows-core",
5101+]
5102+
5103+[[package]]
5104+name = "winapi"
5105+version = "0.3.9"
5106+source = "registry+https://github.com/rust-lang/crates.io-index"
5107+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
5108+dependencies = [
5109+ "winapi-i686-pc-windows-gnu",
5110+ "winapi-x86_64-pc-windows-gnu",
5111+]
5112+
5113+[[package]]
5114+name = "winapi-i686-pc-windows-gnu"
5115+version = "0.4.0"
5116+source = "registry+https://github.com/rust-lang/crates.io-index"
5117+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
5118+
5119+[[package]]
5120+name = "winapi-util"
5121+version = "0.1.11"
5122+source = "registry+https://github.com/rust-lang/crates.io-index"
5123+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
5124+dependencies = [
5125+ "windows-sys 0.61.2",
5126+]
5127+
5128+[[package]]
5129+name = "winapi-x86_64-pc-windows-gnu"
5130+version = "0.4.0"
5131+source = "registry+https://github.com/rust-lang/crates.io-index"
5132+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
5133+
5134+[[package]]
5135+name = "window-vibrancy"
5136+version = "0.8.1"
5137+source = "registry+https://github.com/rust-lang/crates.io-index"
5138+checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580"
5139+dependencies = [
5140+ "objc2",
5141+ "objc2-app-kit",
5142+ "objc2-core-foundation",
5143+ "objc2-foundation",
5144+ "objc2-quartz-core",
5145+ "raw-window-handle",
5146+ "windows-sys 0.61.2",
5147+ "windows-version",
5148+]
5149+
5150+[[package]]
5151+name = "windows"
5152+version = "0.62.2"
5153+source = "registry+https://github.com/rust-lang/crates.io-index"
5154+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
5155+dependencies = [
5156+ "windows-collections",
5157+ "windows-core",
5158+ "windows-future",
5159+ "windows-numerics",
5160+]
5161+
5162+[[package]]
5163+name = "windows-collections"
5164+version = "0.3.2"
5165+source = "registry+https://github.com/rust-lang/crates.io-index"
5166+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
5167+dependencies = [
5168+ "windows-core",
5169+]
5170+
5171+[[package]]
5172+name = "windows-core"
5173+version = "0.62.2"
5174+source = "registry+https://github.com/rust-lang/crates.io-index"
5175+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
5176+dependencies = [
5177+ "windows-implement",
5178+ "windows-interface",
5179+ "windows-link",
5180+ "windows-result",
5181+ "windows-strings",
5182+]
5183+
5184+[[package]]
5185+name = "windows-future"
5186+version = "0.3.2"
5187+source = "registry+https://github.com/rust-lang/crates.io-index"
5188+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
5189+dependencies = [
5190+ "windows-core",
5191+ "windows-link",
5192+ "windows-threading",
5193+]
5194+
5195+[[package]]
5196+name = "windows-implement"
5197+version = "0.60.2"
5198+source = "registry+https://github.com/rust-lang/crates.io-index"
5199+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
5200+dependencies = [
5201+ "proc-macro2",
5202+ "quote",
5203+ "syn 2.0.119",
5204+]
5205+
5206+[[package]]
5207+name = "windows-interface"
5208+version = "0.59.3"
5209+source = "registry+https://github.com/rust-lang/crates.io-index"
5210+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
5211+dependencies = [
5212+ "proc-macro2",
5213+ "quote",
5214+ "syn 2.0.119",
5215+]
5216+
5217+[[package]]
5218+name = "windows-link"
5219+version = "0.2.1"
5220+source = "registry+https://github.com/rust-lang/crates.io-index"
5221+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
5222+
5223+[[package]]
5224+name = "windows-numerics"
5225+version = "0.3.1"
5226+source = "registry+https://github.com/rust-lang/crates.io-index"
5227+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
5228+dependencies = [
5229+ "windows-core",
5230+ "windows-link",
5231+]
5232+
5233+[[package]]
5234+name = "windows-registry"
5235+version = "0.6.1"
5236+source = "registry+https://github.com/rust-lang/crates.io-index"
5237+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
5238+dependencies = [
5239+ "windows-link",
5240+ "windows-result",
5241+ "windows-strings",
5242+]
5243+
5244+[[package]]
5245+name = "windows-result"
5246+version = "0.4.1"
5247+source = "registry+https://github.com/rust-lang/crates.io-index"
5248+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
5249+dependencies = [
5250+ "windows-link",
5251+]
5252+
5253+[[package]]
5254+name = "windows-strings"
5255+version = "0.5.1"
5256+source = "registry+https://github.com/rust-lang/crates.io-index"
5257+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
5258+dependencies = [
5259+ "windows-link",
5260+]
5261+
5262+[[package]]
5263+name = "windows-sys"
5264+version = "0.45.0"
5265+source = "registry+https://github.com/rust-lang/crates.io-index"
5266+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
5267+dependencies = [
5268+ "windows-targets 0.42.2",
5269+]
5270+
5271+[[package]]
5272+name = "windows-sys"
5273+version = "0.52.0"
5274+source = "registry+https://github.com/rust-lang/crates.io-index"
5275+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
5276+dependencies = [
5277+ "windows-targets 0.52.6",
5278+]
5279+
5280+[[package]]
5281+name = "windows-sys"
5282+version = "0.61.2"
5283+source = "registry+https://github.com/rust-lang/crates.io-index"
5284+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
5285+dependencies = [
5286+ "windows-link",
5287+]
5288+
5289+[[package]]
5290+name = "windows-targets"
5291+version = "0.42.2"
5292+source = "registry+https://github.com/rust-lang/crates.io-index"
5293+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
5294+dependencies = [
5295+ "windows_aarch64_gnullvm 0.42.2",
5296+ "windows_aarch64_msvc 0.42.2",
5297+ "windows_i686_gnu 0.42.2",
5298+ "windows_i686_msvc 0.42.2",
5299+ "windows_x86_64_gnu 0.42.2",
5300+ "windows_x86_64_gnullvm 0.42.2",
5301+ "windows_x86_64_msvc 0.42.2",
5302+]
5303+
5304+[[package]]
5305+name = "windows-targets"
5306+version = "0.52.6"
5307+source = "registry+https://github.com/rust-lang/crates.io-index"
5308+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
5309+dependencies = [
5310+ "windows_aarch64_gnullvm 0.52.6",
5311+ "windows_aarch64_msvc 0.52.6",
5312+ "windows_i686_gnu 0.52.6",
5313+ "windows_i686_gnullvm",
5314+ "windows_i686_msvc 0.52.6",
5315+ "windows_x86_64_gnu 0.52.6",
5316+ "windows_x86_64_gnullvm 0.52.6",
5317+ "windows_x86_64_msvc 0.52.6",
5318+]
5319+
5320+[[package]]
5321+name = "windows-threading"
5322+version = "0.2.1"
5323+source = "registry+https://github.com/rust-lang/crates.io-index"
5324+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
5325+dependencies = [
5326+ "windows-link",
5327+]
5328+
5329+[[package]]
5330+name = "windows-version"
5331+version = "0.1.7"
5332+source = "registry+https://github.com/rust-lang/crates.io-index"
5333+checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631"
5334+dependencies = [
5335+ "windows-link",
5336+]
5337+
5338+[[package]]
5339+name = "windows_aarch64_gnullvm"
5340+version = "0.42.2"
5341+source = "registry+https://github.com/rust-lang/crates.io-index"
5342+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
5343+
5344+[[package]]
5345+name = "windows_aarch64_gnullvm"
5346+version = "0.52.6"
5347+source = "registry+https://github.com/rust-lang/crates.io-index"
5348+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
5349+
5350+[[package]]
5351+name = "windows_aarch64_msvc"
5352+version = "0.42.2"
5353+source = "registry+https://github.com/rust-lang/crates.io-index"
5354+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
5355+
5356+[[package]]
5357+name = "windows_aarch64_msvc"
5358+version = "0.52.6"
5359+source = "registry+https://github.com/rust-lang/crates.io-index"
5360+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
5361+
5362+[[package]]
5363+name = "windows_i686_gnu"
5364+version = "0.42.2"
5365+source = "registry+https://github.com/rust-lang/crates.io-index"
5366+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
5367+
5368+[[package]]
5369+name = "windows_i686_gnu"
5370+version = "0.52.6"
5371+source = "registry+https://github.com/rust-lang/crates.io-index"
5372+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
5373+
5374+[[package]]
5375+name = "windows_i686_gnullvm"
5376+version = "0.52.6"
5377+source = "registry+https://github.com/rust-lang/crates.io-index"
5378+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
5379+
5380+[[package]]
5381+name = "windows_i686_msvc"
5382+version = "0.42.2"
5383+source = "registry+https://github.com/rust-lang/crates.io-index"
5384+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
5385+
5386+[[package]]
5387+name = "windows_i686_msvc"
5388+version = "0.52.6"
5389+source = "registry+https://github.com/rust-lang/crates.io-index"
5390+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
5391+
5392+[[package]]
5393+name = "windows_x86_64_gnu"
5394+version = "0.42.2"
5395+source = "registry+https://github.com/rust-lang/crates.io-index"
5396+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
5397+
5398+[[package]]
5399+name = "windows_x86_64_gnu"
5400+version = "0.52.6"
5401+source = "registry+https://github.com/rust-lang/crates.io-index"
5402+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
5403+
5404+[[package]]
5405+name = "windows_x86_64_gnullvm"
5406+version = "0.42.2"
5407+source = "registry+https://github.com/rust-lang/crates.io-index"
5408+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
5409+
5410+[[package]]
5411+name = "windows_x86_64_gnullvm"
5412+version = "0.52.6"
5413+source = "registry+https://github.com/rust-lang/crates.io-index"
5414+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
5415+
5416+[[package]]
5417+name = "windows_x86_64_msvc"
5418+version = "0.42.2"
5419+source = "registry+https://github.com/rust-lang/crates.io-index"
5420+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
5421+
5422+[[package]]
5423+name = "windows_x86_64_msvc"
5424+version = "0.52.6"
5425+source = "registry+https://github.com/rust-lang/crates.io-index"
5426+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
5427+
5428+[[package]]
5429+name = "winnow"
5430+version = "0.5.40"
5431+source = "registry+https://github.com/rust-lang/crates.io-index"
5432+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
5433+dependencies = [
5434+ "memchr",
5435+]
5436+
5437+[[package]]
5438+name = "winnow"
5439+version = "1.0.4"
5440+source = "registry+https://github.com/rust-lang/crates.io-index"
5441+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
5442+dependencies = [
5443+ "memchr",
5444+]
5445+
5446+[[package]]
5447+name = "winreg"
5448+version = "0.56.0"
5449+source = "registry+https://github.com/rust-lang/crates.io-index"
5450+checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
5451+dependencies = [
5452+ "cfg-if",
5453+ "windows-sys 0.61.2",
5454+]
5455+
5456+[[package]]
5457+name = "wit-bindgen"
5458+version = "0.57.1"
5459+source = "registry+https://github.com/rust-lang/crates.io-index"
5460+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
5461+
5462+[[package]]
5463+name = "writeable"
5464+version = "0.6.4"
5465+source = "registry+https://github.com/rust-lang/crates.io-index"
5466+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
5467+
5468+[[package]]
5469+name = "wry"
5470+version = "0.57.0"
5471+source = "registry+https://github.com/rust-lang/crates.io-index"
5472+checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0"
5473+dependencies = [
5474+ "base64 0.22.1",
5475+ "block2",
5476+ "cookie",
5477+ "crossbeam-channel",
5478+ "dirs",
5479+ "dom_query",
5480+ "dpi",
5481+ "dunce",
5482+ "gdkx11",
5483+ "gtk",
5484+ "http",
5485+ "javascriptcore-rs",
5486+ "jni 0.21.1",
5487+ "libc",
5488+ "ndk",
5489+ "objc2",
5490+ "objc2-app-kit",
5491+ "objc2-core-foundation",
5492+ "objc2-foundation",
5493+ "objc2-ui-kit",
5494+ "objc2-web-kit",
5495+ "once_cell",
5496+ "percent-encoding",
5497+ "raw-window-handle",
5498+ "sha2",
5499+ "soup3",
5500+ "tao-macros",
5501+ "thiserror 2.0.21",
5502+ "url",
5503+ "webkit2gtk",
5504+ "webkit2gtk-sys",
5505+ "webview2-com",
5506+ "windows",
5507+ "windows-core",
5508+ "windows-version",
5509+ "x11-dl",
5510+]
5511+
5512+[[package]]
5513+name = "x11"
5514+version = "2.21.0"
5515+source = "registry+https://github.com/rust-lang/crates.io-index"
5516+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
5517+dependencies = [
5518+ "libc",
5519+ "pkg-config",
5520+]
5521+
5522+[[package]]
5523+name = "x11-dl"
5524+version = "2.21.0"
5525+source = "registry+https://github.com/rust-lang/crates.io-index"
5526+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
5527+dependencies = [
5528+ "libc",
5529+ "once_cell",
5530+ "pkg-config",
5531+]
5532+
5533+[[package]]
5534+name = "xattr"
5535+version = "1.6.1"
5536+source = "registry+https://github.com/rust-lang/crates.io-index"
5537+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
5538+dependencies = [
5539+ "libc",
5540+ "rustix",
5541+]
5542+
5543+[[package]]
5544+name = "yoke"
5545+version = "0.8.3"
5546+source = "registry+https://github.com/rust-lang/crates.io-index"
5547+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
5548+dependencies = [
5549+ "stable_deref_trait",
5550+ "yoke-derive",
5551+ "zerofrom",
5552+]
5553+
5554+[[package]]
5555+name = "yoke-derive"
5556+version = "0.8.4"
5557+source = "registry+https://github.com/rust-lang/crates.io-index"
5558+checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
5559+dependencies = [
5560+ "proc-macro2",
5561+ "quote",
5562+ "syn 3.0.7",
5563+ "synstructure",
5564+]
5565+
5566+[[package]]
5567+name = "zbus"
5568+version = "5.19.0"
5569+source = "registry+https://github.com/rust-lang/crates.io-index"
5570+checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
5571+dependencies = [
5572+ "async-broadcast",
5573+ "async-executor",
5574+ "async-io",
5575+ "async-lock",
5576+ "async-process",
5577+ "async-recursion",
5578+ "async-task",
5579+ "async-trait",
5580+ "blocking",
5581+ "enumflags2",
5582+ "event-listener",
5583+ "futures-core",
5584+ "futures-lite",
5585+ "hex",
5586+ "libc",
5587+ "ordered-stream",
5588+ "rustix",
5589+ "serde",
5590+ "serde_repr",
5591+ "tracing",
5592+ "uds_windows",
5593+ "uuid",
5594+ "windows-sys 0.61.2",
5595+ "winnow 1.0.4",
5596+ "zbus_macros",
5597+ "zbus_names",
5598+ "zvariant",
5599+]
5600+
5601+[[package]]
5602+name = "zbus_macros"
5603+version = "5.19.0"
5604+source = "registry+https://github.com/rust-lang/crates.io-index"
5605+checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
5606+dependencies = [
5607+ "proc-macro-crate 3.5.0",
5608+ "proc-macro2",
5609+ "quote",
5610+ "syn 3.0.7",
5611+ "zbus_names",
5612+ "zvariant",
5613+ "zvariant_utils",
5614+]
5615+
5616+[[package]]
5617+name = "zbus_names"
5618+version = "4.3.4"
5619+source = "registry+https://github.com/rust-lang/crates.io-index"
5620+checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
5621+dependencies = [
5622+ "serde",
5623+ "winnow 1.0.4",
5624+ "zvariant",
5625+]
5626+
5627+[[package]]
5628+name = "zcheapstr"
5629+version = "1.1.0"
5630+source = "registry+https://github.com/rust-lang/crates.io-index"
5631+checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
5632+dependencies = [
5633+ "serde",
5634+]
5635+
5636+[[package]]
5637+name = "zerocopy"
5638+version = "0.8.62"
5639+source = "registry+https://github.com/rust-lang/crates.io-index"
5640+checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d"
5641+dependencies = [
5642+ "zerocopy-derive",
5643+]
5644+
5645+[[package]]
5646+name = "zerocopy-derive"
5647+version = "0.8.62"
5648+source = "registry+https://github.com/rust-lang/crates.io-index"
5649+checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a"
5650+dependencies = [
5651+ "proc-macro2",
5652+ "quote",
5653+ "syn 2.0.119",
5654+]
5655+
5656+[[package]]
5657+name = "zerofrom"
5658+version = "0.1.8"
5659+source = "registry+https://github.com/rust-lang/crates.io-index"
5660+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
5661+dependencies = [
5662+ "zerofrom-derive",
5663+]
5664+
5665+[[package]]
5666+name = "zerofrom-derive"
5667+version = "0.1.8"
5668+source = "registry+https://github.com/rust-lang/crates.io-index"
5669+checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
5670+dependencies = [
5671+ "proc-macro2",
5672+ "quote",
5673+ "syn 3.0.7",
5674+ "synstructure",
5675+]
5676+
5677+[[package]]
5678+name = "zeroize"
5679+version = "1.9.1"
5680+source = "registry+https://github.com/rust-lang/crates.io-index"
5681+checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
5682+
5683+[[package]]
5684+name = "zerotrie"
5685+version = "0.2.5"
5686+source = "registry+https://github.com/rust-lang/crates.io-index"
5687+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
5688+dependencies = [
5689+ "displaydoc",
5690+ "yoke",
5691+ "zerofrom",
5692+]
5693+
5694+[[package]]
5695+name = "zerovec"
5696+version = "0.11.8"
5697+source = "registry+https://github.com/rust-lang/crates.io-index"
5698+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
5699+dependencies = [
5700+ "yoke",
5701+ "zerofrom",
5702+ "zerovec-derive",
5703+]
5704+
5705+[[package]]
5706+name = "zerovec-derive"
5707+version = "0.11.6"
5708+source = "registry+https://github.com/rust-lang/crates.io-index"
5709+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
5710+dependencies = [
5711+ "proc-macro2",
5712+ "quote",
5713+ "syn 3.0.7",
5714+]
5715+
5716+[[package]]
5717+name = "zip"
5718+version = "4.6.1"
5719+source = "registry+https://github.com/rust-lang/crates.io-index"
5720+checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
5721+dependencies = [
5722+ "arbitrary",
5723+ "crc32fast",
5724+ "indexmap 2.14.2",
5725+ "memchr",
5726+]
5727+
5728+[[package]]
5729+name = "zlib-rs"
5730+version = "0.6.8"
5731+source = "registry+https://github.com/rust-lang/crates.io-index"
5732+checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
5733+
5734+[[package]]
5735+name = "zmij"
5736+version = "1.0.23"
5737+source = "registry+https://github.com/rust-lang/crates.io-index"
5738+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
5739+
5740+[[package]]
5741+name = "zvariant"
5742+version = "5.15.0"
5743+source = "registry+https://github.com/rust-lang/crates.io-index"
5744+checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
5745+dependencies = [
5746+ "endi",
5747+ "enumflags2",
5748+ "serde",
5749+ "winnow 1.0.4",
5750+ "zcheapstr",
5751+ "zvariant_derive",
5752+ "zvariant_utils",
5753+]
5754+
5755+[[package]]
5756+name = "zvariant_derive"
5757+version = "5.15.0"
5758+source = "registry+https://github.com/rust-lang/crates.io-index"
5759+checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
5760+dependencies = [
5761+ "proc-macro-crate 3.5.0",
5762+ "proc-macro2",
5763+ "quote",
5764+ "syn 3.0.7",
5765+ "zvariant_utils",
5766+]
5767+
5768+[[package]]
5769+name = "zvariant_utils"
5770+version = "4.2.0"
5771+source = "registry+https://github.com/rust-lang/crates.io-index"
5772+checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
5773+dependencies = [
5774+ "proc-macro2",
5775+ "quote",
5776+ "serde",
5777+ "syn 3.0.7",
5778+ "winnow 1.0.4",
5779+]
+43−0
1+[package]
2+name = "g1t-desktop"
3+version = "0.1.0"
4+edition = "2024"
5+license = "MIT"
6+repository = "https://g1t.sh/flagon-io/g1t"
7+description = "The g1t desktop app: g1t in a window of its own, with native notifications, a badge, a tray, g1t:// links and updates that install themselves."
8+
9+# Not a member of the repository's Cargo workspace (its Cargo.toml excludes
10+# this folder): Tauri needs a toolkit the services never do, so it builds
11+# on its own, with its own lockfile, and `cargo test --workspace` stays
12+# fast. Build it from apps/desktop with `npm run build`.
13+[workspace]
14+
15+[build-dependencies]
16+tauri-build = { version = "2", features = [] }
17+
18+[dependencies]
19+tauri = { version = "2", features = ["tray-icon", "image-png"] }
20+# Updates: a signed manifest at g1t.sh/downloads/desktop/latest.json.
21+tauri-plugin-updater = "2"
22+# Native notifications, from the site's live feed.
23+tauri-plugin-notification = "2"
24+# Links out, opened in the browser the system uses.
25+tauri-plugin-opener = "2"
26+# g1t:// links, which open the page they name; a second launch hands its
27+# link to the app already running.
28+tauri-plugin-deep-link = "2"
29+tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
30+# The window's size and place, kept between launches.
31+tauri-plugin-window-state = "2"
32+serde = { version = "1", features = ["derive"] }
33+serde_json = "1"
34+url = "2"
35+# Whether the server answers, before the window goes there (the same client the updater brings).
36+reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
37+tokio = { version = "1", features = ["time"] }
38+
39+[profile.release]
40+strip = "debuginfo"
41+lto = "thin"
42+opt-level = "s"
43+codegen-units = 1
+3−0
1+fn main() {
2+ tauri_build::build()
3+}
+7−0
1+{
2+ "$schema": "../gen/schemas/desktop-schema.json",
3+ "identifier": "launcher",
4+ "description": "The launcher page bundled with the app, which finds the server and goes there. The site's own pages get their capability at run time, for the server that is configured (src/config.rs).",
5+ "windows": ["main"],
6+ "permissions": ["core:default"]
7+}
+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+58−0
1+//! The badge: what needs the person, on the app's icon and in the tray's
2+//! hover text. macOS and Linux draw a count on the icon; Windows has no
3+//! count, so it gets a lavender dot over the taskbar icon instead.
4+
5+use tauri::{AppHandle, Manager};
6+
7+use crate::{WINDOW, config, tray};
8+
9+pub fn set(app: &AppHandle, count: i64) {
10+ *app.state::<config::State>().waiting.lock().unwrap() = count;
11+ if let Some(window) = app.get_webview_window(WINDOW) {
12+ #[cfg(target_os = "windows")]
13+ let _ = window.set_overlay_icon(if count > 0 { Some(dot()) } else { None });
14+ #[cfg(not(target_os = "windows"))]
15+ let _ = window.set_badge_count(if count > 0 { Some(count) } else { None });
16+ }
17+ if let Some(tray) = app.tray_by_id(tray::TRAY) {
18+ let text = match count {
19+ 0 => "g1t".to_string(),
20+ 1 => "g1t · 1 thing waiting for you".to_string(),
21+ n => format!("g1t · {n} things waiting for you"),
22+ };
23+ let _ = tray.set_tooltip(Some(text));
24+ }
25+}
26+
27+/// A lavender dot with a dark edge, 32 pixels square, drawn here so
28+/// nothing needs loading.
29+#[cfg(target_os = "windows")]
30+fn dot() -> tauri::image::Image<'static> {
31+ const SIZE: usize = 32;
32+ let mut rgba = vec![0u8; SIZE * SIZE * 4];
33+ let centre = (SIZE as f32 - 1.0) / 2.0;
34+ let radius = SIZE as f32 / 2.0;
35+ for y in 0..SIZE {
36+ for x in 0..SIZE {
37+ let dx = x as f32 - centre;
38+ let dy = y as f32 - centre;
39+ let distance = (dx * dx + dy * dy).sqrt();
40+ // Lavender inside, a thin dark ring, then nothing; the edges blend.
41+ let (colour, alpha) = if distance <= radius - 3.0 {
42+ ([0xb6, 0xa8, 0xff], 1.0)
43+ } else if distance <= radius - 0.5 {
44+ ([0x0f, 0x0f, 0x11], 1.0)
45+ } else if distance <= radius + 0.5 {
46+ ([0x0f, 0x0f, 0x11], radius + 0.5 - distance)
47+ } else {
48+ ([0, 0, 0], 0.0)
49+ };
50+ let at = (y * SIZE + x) * 4;
51+ rgba[at] = colour[0];
52+ rgba[at + 1] = colour[1];
53+ rgba[at + 2] = colour[2];
54+ rgba[at + 3] = (alpha.clamp(0.0, 1.0) * 255.0) as u8;
55+ }
56+ }
57+ tauri::image::Image::new_owned(rgba, SIZE as u32, SIZE as u32)
58+}
+121−0
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__";
12+
13+ function invoke(command, args) {
14+ var core = window.__TAURI__ && window.__TAURI__.core;
15+ if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
16+ var internals = window.__TAURI_INTERNALS__;
17+ if (internals && typeof internals.invoke === "function") return internals.invoke(command, args || {});
18+ return Promise.reject(new Error("g1t desktop: this page cannot reach the app"));
19+ }
20+
21+ function quietly(promise) {
22+ promise.catch(function (error) {
23+ console.warn("g1t desktop:", error && error.message ? error.message : error);
24+ });
25+ }
26+
27+ var listeners = [];
28+ var listening = false;
29+ function listen() {
30+ if (listening) return;
31+ listening = true;
32+ var events = window.__TAURI__ && window.__TAURI__.event;
33+ if (!events || typeof events.listen !== "function") return;
34+ events
35+ .listen("g1t-navigate", function (event) {
36+ var href = event && event.payload;
37+ if (typeof href !== "string") return;
38+ listeners.slice().forEach(function (listener) {
39+ try {
40+ listener(href);
41+ } catch (error) {
42+ console.warn("g1t desktop: a navigate listener failed", error);
43+ }
44+ });
45+ })
46+ .catch(function () {});
47+ }
48+
49+ var bridge = {
50+ version: VERSION,
51+ platform: PLATFORM,
52+ // A native notification, for a message to the person while the window is behind another.
53+ notify: function (notification) {
54+ quietly(invoke("notify", { title: String(notification.title || "g1t"), body: String(notification.body || "") }));
55+ },
56+ // What needs the person, on the icon and the tray; 0 clears it.
57+ setBadge: function (count) {
58+ quietly(invoke("set_badge", { count: Math.max(0, Math.floor(Number(count) || 0)) }));
59+ },
60+ // A link to somewhere else, in the browser the system uses.
61+ openUrl: function (url) {
62+ quietly(invoke("open_url", { url: String(url) }));
63+ },
64+ // g1t:// links, as site paths such as "/acme/-/chat/dm/chn_1".
65+ onNavigate: function (listener) {
66+ listeners.push(listener);
67+ listen();
68+ return function () {
69+ var at = listeners.indexOf(listener);
70+ if (at >= 0) listeners.splice(at, 1);
71+ };
72+ },
73+ // Looks for an update now: "current", "ready:<version>", or rejects with why.
74+ checkForUpdates: function () {
75+ return invoke("check_for_updates");
76+ },
77+ };
78+ Object.defineProperty(window, "g1tDesktop", { value: Object.freeze(bridge), writable: false, configurable: false });
79+
80+ // Links out. A link to another site opens in the system's browser, and
81+ // the window stays on g1t; a link that asks for a new tab, or a click
82+ // with Ctrl or Cmd held, goes on in this window, which has no tabs.
83+ function destination(href) {
84+ try {
85+ var url = new URL(href, location.href);
86+ return /^https?:$/.test(url.protocol) ? url : null;
87+ } catch (error) {
88+ return null;
89+ }
90+ }
91+ document.addEventListener(
92+ "click",
93+ function (event) {
94+ if (event.defaultPrevented || event.button !== 0) return;
95+ var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
96+ if (!anchor || anchor.hasAttribute("download")) return;
97+ var url = destination(anchor.getAttribute("href"));
98+ if (!url) return;
99+ if (url.origin !== location.origin) {
100+ event.preventDefault();
101+ event.stopImmediatePropagation();
102+ bridge.openUrl(url.href);
103+ } else if (anchor.target === "_blank" || event.metaKey || event.ctrlKey) {
104+ event.preventDefault();
105+ event.stopImmediatePropagation();
106+ location.assign(url.href);
107+ }
108+ },
109+ true,
110+ );
111+ var open = window.open;
112+ window.open = function (href, target, features) {
113+ var url = destination(href);
114+ if (url) {
115+ if (url.origin === location.origin) location.assign(url.href);
116+ else bridge.openUrl(url.href);
117+ return null;
118+ }
119+ return typeof open === "function" ? open.call(window, href, target, features) : null;
120+ };
121+})();
+121−0
1+//! What the site may ask of the app: the commands behind `window.g1tDesktop`
2+//! (bridge.js), and the launcher's own.
3+//!
4+//! Every command checks who is asking. The site's commands answer only a
5+//! page of the configured server; the launcher's only the page bundled
6+//! with the app. A page from anywhere else gets nothing, whatever it
7+//! loads.
8+
9+use serde::Serialize;
10+use tauri::{AppHandle, Webview};
11+use tauri_plugin_notification::NotificationExt;
12+use tauri_plugin_opener::OpenerExt;
13+use url::Url;
14+
15+use crate::{badge, config, updates};
16+
17+const SCRIPT: &str = include_str!("bridge.js");
18+
19+/// The bridge, run at the start of every page the window loads.
20+pub fn script() -> String {
21+ SCRIPT.replace("__VERSION__", env!("CARGO_PKG_VERSION")).replace("__PLATFORM__", std::env::consts::OS)
22+}
23+
24+/// What the site and the launcher learn about the app.
25+#[derive(Serialize)]
26+pub struct Info {
27+ pub version: String,
28+ pub platform: String,
29+ pub server: String,
30+ /// The version downloaded and waiting for a restart, if one is.
31+ pub update_ready: Option<String>,
32+}
33+
34+fn caller(webview: &Webview) -> Result<Url, String> {
35+ webview.url().map_err(|error| error.to_string())
36+}
37+
38+/// The page asking is one of the server's.
39+fn from_site(app: &AppHandle, webview: &Webview) -> Result<(), String> {
40+ let url = caller(webview)?;
41+ if config::is_server(app, &url) { Ok(()) } else { Err("only g1t's own pages may ask that".into()) }
42+}
43+
44+/// The page asking is the launcher.
45+fn from_launcher(webview: &Webview) -> Result<(), String> {
46+ let url = caller(webview)?;
47+ if config::is_launcher(&url) { Ok(()) } else { Err("only the launcher may ask that".into()) }
48+}
49+
50+fn from_either(app: &AppHandle, webview: &Webview) -> Result<(), String> {
51+ from_site(app, webview).or_else(|_| from_launcher(webview))
52+}
53+
54+#[tauri::command]
55+pub fn desktop_info(app: AppHandle, webview: Webview) -> Result<Info, String> {
56+ from_either(&app, &webview)?;
57+ Ok(Info {
58+ version: env!("CARGO_PKG_VERSION").into(),
59+ platform: std::env::consts::OS.into(),
60+ server: config::server(&app).to_string(),
61+ update_ready: updates::ready(&app),
62+ })
63+}
64+
65+/// A notification from the system: the site asks for one when a message
66+/// is for the person and the window is behind another.
67+#[tauri::command]
68+pub fn notify(app: AppHandle, webview: Webview, title: String, body: String) -> Result<(), String> {
69+ from_site(&app, &webview)?;
70+ app.notification().builder().title(title).body(body).show().map_err(|error| error.to_string())
71+}
72+
73+/// What needs the person, on the icon and the tray; 0 clears it.
74+#[tauri::command]
75+pub fn set_badge(app: AppHandle, webview: Webview, count: i64) -> Result<(), String> {
76+ from_site(&app, &webview)?;
77+ badge::set(&app, count.max(0));
78+ Ok(())
79+}
80+
81+/// A link to somewhere else, opened in the browser the system uses.
82+#[tauri::command]
83+pub fn open_url(app: AppHandle, webview: Webview, url: String) -> Result<(), String> {
84+ from_either(&app, &webview)?;
85+ let parsed = Url::parse(&url).map_err(|error| error.to_string())?;
86+ if !matches!(parsed.scheme(), "http" | "https" | "mailto") {
87+ return Err(format!("{} links stay in the app", parsed.scheme()));
88+ }
89+ app.opener().open_url(parsed.as_str(), None::<&str>).map_err(|error| error.to_string())
90+}
91+
92+/// Whether a server answers at all: the launcher asks before the window
93+/// goes there, so a machine with no network sees a page of ours, not the
94+/// web view's own error.
95+#[tauri::command]
96+pub async fn reachable(app: AppHandle, webview: Webview, url: Option<String>) -> Result<bool, String> {
97+ from_launcher(&webview)?;
98+ let target = match url {
99+ Some(text) => config::parse(&text).ok_or("That is not a web address.")?,
100+ None => config::server(&app),
101+ };
102+ let client = reqwest::Client::builder()
103+ .timeout(std::time::Duration::from_secs(6))
104+ .build()
105+ .map_err(|error| error.to_string())?;
106+ Ok(client.head(target).send().await.is_ok())
107+}
108+
109+/// The launcher chose a server: it is kept, and the window goes there next.
110+#[tauri::command]
111+pub fn set_server(app: AppHandle, webview: Webview, url: String) -> Result<String, String> {
112+ from_launcher(&webview)?;
113+ config::set_server(&app, &url).map(|server| server.to_string())
114+}
115+
116+/// Looks for an update now: `current`, `ready:<version>`, or an error.
117+#[tauri::command]
118+pub async fn check_for_updates(app: AppHandle, webview: Webview) -> Result<String, String> {
119+ from_either(&app, &webview)?;
120+ updates::check(&app, true).await
121+}
+155−0
1+//! What the app keeps between launches, and what it knows while running.
2+//!
3+//! The one setting is the server: `https://g1t.sh` unless someone runs a
4+//! g1t of their own, kept in `desktop.json` in the app's config folder
5+//! (`~/.config/g1t` on Linux, `~/Library/Application Support/sh.g1t.desktop`
6+//! on macOS, `%APPDATA%\sh.g1t.desktop` on Windows). The server's pages are
7+//! the only remote pages allowed to talk to the app (`allow_site`).
8+
9+use std::fs;
10+use std::path::PathBuf;
11+use std::sync::Mutex;
12+
13+use serde::{Deserialize, Serialize};
14+use tauri::ipc::CapabilityBuilder;
15+use tauri::{AppHandle, Manager};
16+use url::Url;
17+
18+use crate::WINDOW;
19+
20+pub const DEFAULT_SERVER: &str = "https://g1t.sh";
21+
22+/// What the running app knows: the server, where the window is, and what
23+/// the updater has ready.
24+#[derive(Default)]
25+pub struct State {
26+ /// The server, as an origin with a trailing slash: `https://g1t.sh/`.
27+ pub server: Mutex<Option<Url>>,
28+ /// The page the window last started loading.
29+ pub at: Mutex<Option<Url>>,
30+ /// Origins whose pages may call the app, each given its capability once.
31+ pub allowed: Mutex<Vec<String>>,
32+ /// An update downloaded and waiting to be installed, and its version.
33+ pub update: Mutex<Option<updates::Ready>>,
34+ /// What the badge shows.
35+ pub waiting: Mutex<i64>,
36+}
37+
38+use crate::updates;
39+
40+#[derive(Default, Serialize, Deserialize)]
41+struct File {
42+ #[serde(default)]
43+ server: Option<String>,
44+}
45+
46+fn path(app: &AppHandle) -> Option<PathBuf> {
47+ app.path().app_config_dir().ok().map(|dir| dir.join("desktop.json"))
48+}
49+
50+/// A server as typed: http or https, with a host; only its origin is kept.
51+pub fn parse(text: &str) -> Option<Url> {
52+ let text = text.trim();
53+ let text = if text.contains("://") { text.to_string() } else { format!("https://{text}") };
54+ let mut url = Url::parse(&text).ok()?;
55+ if !matches!(url.scheme(), "http" | "https") || url.host_str().is_none() {
56+ return None;
57+ }
58+ url.set_path("/");
59+ url.set_query(None);
60+ url.set_fragment(None);
61+ Some(url)
62+}
63+
64+/// Reads the saved server, or takes g1t.sh.
65+pub fn load(app: &AppHandle) {
66+ let file: File = path(app)
67+ .and_then(|path| fs::read(path).ok())
68+ .and_then(|bytes| serde_json::from_slice(&bytes).ok())
69+ .unwrap_or_default();
70+ let server = file.server.as_deref().and_then(parse).unwrap_or_else(|| Url::parse(DEFAULT_SERVER).expect("the default server is a URL"));
71+ *app.state::<State>().server.lock().unwrap() = Some(server);
72+}
73+
74+pub fn server(app: &AppHandle) -> Url {
75+ app.state::<State>()
76+ .server
77+ .lock()
78+ .unwrap()
79+ .clone()
80+ .unwrap_or_else(|| Url::parse(DEFAULT_SERVER).expect("the default server is a URL"))
81+}
82+
83+/// Saves a new server and lets its pages talk to the app.
84+pub fn set_server(app: &AppHandle, text: &str) -> Result<Url, String> {
85+ let server = parse(text).ok_or("That is not a web address. It looks like https://g1t.example.com.")?;
86+ let file = File { server: Some(server.to_string()) };
87+ let path = path(app).ok_or("The app has no folder to keep its settings in.")?;
88+ if let Some(dir) = path.parent() {
89+ fs::create_dir_all(dir).map_err(|error| error.to_string())?;
90+ }
91+ fs::write(&path, serde_json::to_vec_pretty(&file).map_err(|error| error.to_string())?).map_err(|error| error.to_string())?;
92+ *app.state::<State>().server.lock().unwrap() = Some(server.clone());
93+ allow_site(app).map_err(|error| error.to_string())?;
94+ Ok(server)
95+}
96+
97+pub fn origin(url: &Url) -> String {
98+ url.origin().ascii_serialization()
99+}
100+
101+/// Whether a page is the server's.
102+pub fn is_server(app: &AppHandle, url: &Url) -> bool {
103+ matches!(url.scheme(), "http" | "https") && origin(url) == origin(&server(app))
104+}
105+
106+/// Whether a page is the launcher, bundled with the app.
107+pub fn is_launcher(url: &Url) -> bool {
108+ url.scheme() == "tauri" || (matches!(url.scheme(), "http" | "https") && url.host_str() == Some("tauri.localhost"))
109+}
110+
111+/// Lets the server's pages call the app: a capability for its origin,
112+/// added once. Pages from anywhere else get no IPC at all, and bridge.rs
113+/// checks the caller again on every command.
114+pub fn allow_site(app: &AppHandle) -> tauri::Result<()> {
115+ let origin = origin(&server(app));
116+ let state = app.state::<State>();
117+ let mut allowed = state.allowed.lock().unwrap();
118+ if allowed.iter().any(|known| *known == origin) {
119+ return Ok(());
120+ }
121+ app.add_capability(
122+ CapabilityBuilder::new(format!("site-{}", allowed.len()))
123+ .window(WINDOW)
124+ .remote(format!("{origin}/*"))
125+ .permission("core:event:allow-listen")
126+ .permission("core:event:allow-unlisten"),
127+ )?;
128+ allowed.push(origin);
129+ Ok(())
130+}
131+
132+/// The window started loading a page.
133+pub fn at(app: &AppHandle, url: Url) {
134+ *app.state::<State>().at.lock().unwrap() = Some(url);
135+}
136+
137+/// Whether the window is on one of the server's pages now.
138+pub fn on_site(app: &AppHandle) -> bool {
139+ app.state::<State>().at.lock().unwrap().as_ref().is_some_and(|url| is_server(app, url))
140+}
141+
142+#[cfg(test)]
143+mod tests {
144+ use super::parse;
145+
146+ #[test]
147+ fn a_server_is_its_origin() {
148+ assert_eq!(parse("https://g1t.sh/acme/-/chat?x#y").unwrap().as_str(), "https://g1t.sh/");
149+ assert_eq!(parse(" g1t.example.com ").unwrap().as_str(), "https://g1t.example.com/");
150+ assert_eq!(parse("http://localhost:5173/login").unwrap().as_str(), "http://localhost:5173/");
151+ assert!(parse("ftp://g1t.sh").is_none());
152+ assert!(parse("not a url").is_none());
153+ assert!(parse("").is_none());
154+ }
155+}
+86−0
1+//! `g1t://` links. `g1t://acme/-/chat/dm/chn_1` opens that page on the
2+//! server: the site, when the window is on it, is told the path and moves
3+//! without a reload; otherwise the window loads the page. A link that
4+//! started the app is read on the way up; one that arrives while it runs
5+//! comes through the deep-link plugin, from a second launch too.
6+
7+use tauri::{AppHandle, Emitter, Manager};
8+use tauri_plugin_deep_link::DeepLinkExt;
9+use url::Url;
10+
11+use crate::{WINDOW, config, tray};
12+
13+/// What the page listens for (bridge.js).
14+pub const EVENT: &str = "g1t-navigate";
15+
16+pub fn install(app: &AppHandle) {
17+ // Installers register the scheme; a build run from the source tree,
18+ // and an AppImage, need it done here.
19+ #[cfg(any(windows, target_os = "linux"))]
20+ let _ = app.deep_link().register_all();
21+ let handle = app.clone();
22+ app.deep_link().on_open_url(move |event| {
23+ for url in event.urls() {
24+ open(&handle, &url);
25+ }
26+ });
27+ if let Ok(Some(urls)) = app.deep_link().get_current() {
28+ for url in urls {
29+ open(app, &url);
30+ }
31+ }
32+}
33+
34+/// The site path a link names: `g1t://acme/-/chat?x` is `/acme/-/chat?x`.
35+pub fn path_of(url: &Url) -> Option<String> {
36+ if url.scheme() != "g1t" {
37+ return None;
38+ }
39+ let mut path = match url.host_str() {
40+ Some(host) if !host.is_empty() => format!("/{host}{}", url.path()),
41+ _ => url.path().to_string(),
42+ };
43+ if path.is_empty() || !path.starts_with('/') {
44+ path.insert(0, '/');
45+ }
46+ if let Some(query) = url.query() {
47+ path.push('?');
48+ path.push_str(query);
49+ }
50+ Some(path)
51+}
52+
53+pub fn open(app: &AppHandle, url: &Url) {
54+ if let Some(path) = path_of(url) {
55+ go(app, &path);
56+ }
57+}
58+
59+/// Takes the window to a path on the server.
60+pub fn go(app: &AppHandle, path: &str) {
61+ tray::show(app);
62+ if config::on_site(app) {
63+ let _ = app.emit_to(WINDOW, EVENT, path.to_string());
64+ return;
65+ }
66+ if let Some(window) = app.get_webview_window(WINDOW) {
67+ if let Ok(target) = config::server(app).join(path.trim_start_matches('/')) {
68+ let _ = window.navigate(target);
69+ }
70+ }
71+}
72+
73+#[cfg(test)]
74+mod tests {
75+ use super::path_of;
76+ use url::Url;
77+
78+ #[test]
79+ fn a_link_names_a_site_path() {
80+ let path = |text: &str| path_of(&Url::parse(text).unwrap());
81+ assert_eq!(path("g1t://acme/-/chat/dm/chn_1").as_deref(), Some("/acme/-/chat/dm/chn_1"));
82+ assert_eq!(path("g1t://acme/hello/pull/4?tab=files").as_deref(), Some("/acme/hello/pull/4?tab=files"));
83+ assert_eq!(path("g1t://notifications").as_deref(), Some("/notifications"));
84+ assert_eq!(path("https://g1t.sh/acme"), None);
85+ }
86+}
+95−0
1+//! The g1t desktop app: g1t.sh, or a g1t of your own, in a window of its
2+//! own. The window loads the live site, so every change to g1t reaches it
3+//! the moment it is deployed and nothing here needs a new build for it;
4+//! only what is native lives in this program: notifications from the
5+//! system, a badge on the icon, a tray, `g1t://` links, the server to use,
6+//! and updates that install themselves (docs.g1t.sh/guides/desktop/).
7+//!
8+//! The site finds the app at `window.g1tDesktop` (bridge.js), the one
9+//! contract between the two; what the page asks of it is answered by the
10+//! commands in bridge.rs, each of which checks that the page asking is the
11+//! configured server's.
12+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
13+
14+mod badge;
15+mod bridge;
16+mod config;
17+mod links;
18+mod navigation;
19+mod tray;
20+mod updates;
21+
22+use tauri::{RunEvent, WebviewUrl, WebviewWindowBuilder, WindowEvent};
23+use tauri_plugin_window_state::{StateFlags, WindowExt};
24+
25+/// The one window's label.
26+pub const WINDOW: &str = "main";
27+
28+fn main() {
29+ tauri::Builder::default()
30+ // A second launch (a g1t:// link, or the icon clicked again) goes to
31+ // the app already running: the link reaches links.rs through the
32+ // deep-link plugin, and the window comes forward.
33+ .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| tray::show(app)))
34+ .plugin(tauri_plugin_deep_link::init())
35+ .plugin(tauri_plugin_notification::init())
36+ .plugin(tauri_plugin_opener::init())
37+ .plugin(tauri_plugin_updater::Builder::new().build())
38+ .plugin(tauri_plugin_window_state::Builder::new().build())
39+ .manage(config::State::default())
40+ .invoke_handler(tauri::generate_handler![
41+ bridge::desktop_info,
42+ bridge::notify,
43+ bridge::set_badge,
44+ bridge::open_url,
45+ bridge::reachable,
46+ bridge::set_server,
47+ bridge::check_for_updates,
48+ ])
49+ .setup(|app| {
50+ let handle = app.handle().clone();
51+ config::load(&handle);
52+ config::allow_site(&handle)?;
53+ let window = WebviewWindowBuilder::new(app, WINDOW, WebviewUrl::App("index.html".into()))
54+ .title("g1t")
55+ .inner_size(1280.0, 840.0)
56+ .min_inner_size(720.0, 480.0)
57+ .initialization_script(bridge::script())
58+ .on_navigation({
59+ let handle = handle.clone();
60+ move |url| navigation::allowed(&handle, url)
61+ })
62+ .on_page_load({
63+ let handle = handle.clone();
64+ move |_webview, payload| config::at(&handle, payload.url().clone())
65+ })
66+ .build()?;
67+ let _ = window.restore_state(StateFlags::all());
68+ // Closing the window keeps g1t running, for notifications; Quit in the tray ends it.
69+ window.on_window_event({
70+ let handle = handle.clone();
71+ move |event| {
72+ if let WindowEvent::CloseRequested { api, .. } = event {
73+ api.prevent_close();
74+ tray::hide(&handle);
75+ }
76+ }
77+ });
78+ tray::install(&handle)?;
79+ links::install(&handle);
80+ updates::schedule(handle);
81+ Ok(())
82+ })
83+ .build(tauri::generate_context!())
84+ .expect("g1t could not start")
85+ .run(|app, event| match event {
86+ // The dock icon, clicked while the window is hidden.
87+ #[cfg(target_os = "macos")]
88+ RunEvent::Reopen { .. } => tray::show(app),
89+ // No window open is not a reason to quit: the tray is still there.
90+ RunEvent::ExitRequested { api, code: None, .. } => api.prevent_exit(),
91+ _ => {
92+ let _ = app;
93+ }
94+ });
95+}
+29−0
1+//! Where the window may go: the launcher, the server's pages, and the
2+//! pages that sign someone in there. Any other address opens in the
3+//! browser the system uses, and the window stays where it is.
4+
5+use tauri::AppHandle;
6+use tauri_plugin_opener::OpenerExt;
7+use url::Url;
8+
9+use crate::config;
10+
11+/// Signing in with GitHub goes there and comes back.
12+const SIGN_IN_HOSTS: &[&str] = &["github.com", "www.github.com"];
13+
14+/// Whether the window may load `url`.
15+pub fn allowed(app: &AppHandle, url: &Url) -> bool {
16+ match url.scheme() {
17+ "about" | "blob" | "data" => return true,
18+ "http" | "https" | "tauri" => {}
19+ _ => return false,
20+ }
21+ if config::is_launcher(url) || config::is_server(app, url) {
22+ return true;
23+ }
24+ if url.host_str().is_some_and(|host| SIGN_IN_HOSTS.contains(&host)) {
25+ return true;
26+ }
27+ let _ = app.opener().open_url(url.as_str(), None::<&str>);
28+ false
29+}
+106−0
1+//! The tray icon and its menu, and showing and hiding the window. Closing
2+//! the window hides it; g1t keeps running for notifications, and the
3+//! tray's Open, or the icon, brings it back. Quit is here too.
4+
5+use std::sync::OnceLock;
6+
7+use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
8+use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
9+use tauri::{AppHandle, Manager, Wry};
10+
11+use crate::{WINDOW, updates};
12+
13+pub const TRAY: &str = "g1t";
14+
15+/// The menu row that restarts into a downloaded update: off until one is.
16+static RESTART: OnceLock<MenuItem<Wry>> = OnceLock::new();
17+
18+pub fn install(app: &AppHandle) -> tauri::Result<()> {
19+ let open = MenuItem::with_id(app, "open", "Open g1t", true, None::<&str>)?;
20+ let check = MenuItem::with_id(app, "check", "Check for updates…", true, None::<&str>)?;
21+ let restart = MenuItem::with_id(app, "restart", "Restart to finish updating", false, None::<&str>)?;
22+ let server = MenuItem::with_id(app, "server", "Use another g1t…", true, None::<&str>)?;
23+ let quit = MenuItem::with_id(app, "quit", "Quit g1t", true, None::<&str>)?;
24+ let menu = Menu::with_items(
25+ app,
26+ &[
27+ &open,
28+ &PredefinedMenuItem::separator(app)?,
29+ &check,
30+ &restart,
31+ &PredefinedMenuItem::separator(app)?,
32+ &server,
33+ &PredefinedMenuItem::separator(app)?,
34+ &quit,
35+ ],
36+ )?;
37+ let _ = RESTART.set(restart);
38+ let mut tray = TrayIconBuilder::with_id(TRAY)
39+ .menu(&menu)
40+ .show_menu_on_left_click(false)
41+ .tooltip("g1t")
42+ .on_menu_event(|app, event| match event.id().as_ref() {
43+ "open" => show(app),
44+ "check" => {
45+ let app = app.clone();
46+ tauri::async_runtime::spawn(async move {
47+ let _ = updates::check(&app, true).await;
48+ });
49+ }
50+ "restart" => updates::restart(app),
51+ "server" => launcher(app, "change"),
52+ "quit" => updates::quit(app),
53+ _ => {}
54+ })
55+ .on_tray_icon_event(|tray, event| {
56+ if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event {
57+ show(tray.app_handle());
58+ }
59+ });
60+ if let Some(icon) = app.default_window_icon() {
61+ tray = tray.icon(icon.clone());
62+ }
63+ tray.build(app)?;
64+ Ok(())
65+}
66+
67+/// The update is downloaded: the menu says so, and offers the restart.
68+pub fn update_ready(version: &str) {
69+ if let Some(item) = RESTART.get() {
70+ let _ = item.set_text(format!("Restart to update to {version}"));
71+ let _ = item.set_enabled(true);
72+ }
73+}
74+
75+pub fn show(app: &AppHandle) {
76+ if let Some(window) = app.get_webview_window(WINDOW) {
77+ let _ = window.show();
78+ let _ = window.unminimize();
79+ let _ = window.set_focus();
80+ }
81+}
82+
83+pub fn hide(app: &AppHandle) {
84+ if let Some(window) = app.get_webview_window(WINDOW) {
85+ let _ = window.hide();
86+ }
87+}
88+
89+/// The address of the launcher page bundled with the app.
90+pub fn launcher_url(query: &str) -> url::Url {
91+ let base = if cfg!(windows) { "http://tauri.localhost/" } else { "tauri://localhost/" };
92+ let mut url = url::Url::parse(base).expect("the launcher's address is a URL");
93+ url.set_path("/index.html");
94+ if !query.is_empty() {
95+ url.set_query(Some(query));
96+ }
97+ url
98+}
99+
100+/// Takes the window to the launcher: `change` to choose another server.
101+pub fn launcher(app: &AppHandle, query: &str) {
102+ if let Some(window) = app.get_webview_window(WINDOW) {
103+ let _ = window.navigate(launcher_url(query));
104+ show(app);
105+ }
106+}
+116−0
1+//! Updates. The app asks g1t.sh/downloads/desktop/latest.json soon after
2+//! it starts and every six hours after, downloads a newer version in the
3+//! background, and then says so: a notification, and the tray's "Restart
4+//! to update". Nothing is installed under someone mid-sentence; the
5+//! restart, or quitting, installs it. The manifest is signed with g1t's
6+//! release key, built into the app, and a download whose signature does
7+//! not check out is thrown away (scripts/desktop-release.mjs).
8+
9+use std::time::Duration;
10+
11+use tauri::AppHandle;
12+use tauri::Manager;
13+use tauri_plugin_notification::NotificationExt;
14+use tauri_plugin_updater::{Update, UpdaterExt};
15+
16+use crate::{config, tray};
17+
18+/// An update downloaded and checked, waiting to be installed.
19+pub struct Ready {
20+ pub version: String,
21+ update: Update,
22+ bytes: Vec<u8>,
23+}
24+
25+const FIRST_CHECK: Duration = Duration::from_secs(15);
26+const EVERY: Duration = Duration::from_secs(6 * 60 * 60);
27+
28+/// Checks soon after the start, then every six hours.
29+pub fn schedule(app: AppHandle) {
30+ tauri::async_runtime::spawn(async move {
31+ tokio::time::sleep(FIRST_CHECK).await;
32+ loop {
33+ let _ = check(&app, false).await;
34+ tokio::time::sleep(EVERY).await;
35+ }
36+ });
37+}
38+
39+/// The version downloaded and waiting, if one is.
40+pub fn ready(app: &AppHandle) -> Option<String> {
41+ app.state::<config::State>().update.lock().unwrap().as_ref().map(|ready| ready.version.clone())
42+}
43+
44+fn tell(app: &AppHandle, title: &str, body: &str) {
45+ let _ = app.notification().builder().title(title).body(body).show();
46+}
47+
48+/// Looks for an update and downloads it. `asked` is a person asking from
49+/// the tray or the site, who is told the answer either way; the timer
50+/// says nothing unless there is something to install.
51+pub async fn check(app: &AppHandle, asked: bool) -> Result<String, String> {
52+ if let Some(version) = ready(app) {
53+ if asked {
54+ tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t to use it."));
55+ }
56+ return Ok(format!("ready:{version}"));
57+ }
58+ let updater = app.updater().map_err(|error| error.to_string())?;
59+ let update = match updater.check().await {
60+ Ok(Some(update)) => update,
61+ Ok(None) => {
62+ if asked {
63+ tell(app, "g1t is up to date", &format!("You have version {}.", env!("CARGO_PKG_VERSION")));
64+ }
65+ return Ok("current".into());
66+ }
67+ Err(error) => {
68+ if asked {
69+ tell(app, "g1t could not check for updates", &error.to_string());
70+ }
71+ return Err(error.to_string());
72+ }
73+ };
74+ let version = update.version.clone();
75+ match update.download(|_, _| {}, || {}).await {
76+ Ok(bytes) => {
77+ *app.state::<config::State>().update.lock().unwrap() = Some(Ready { version: version.clone(), update, bytes });
78+ tray::update_ready(&version);
79+ tell(app, "g1t is ready to update", &format!("Version {version} is downloaded. Restart g1t from the tray to use it."));
80+ Ok(format!("ready:{version}"))
81+ }
82+ Err(error) => {
83+ if asked {
84+ tell(app, "The update could not be downloaded", &error.to_string());
85+ }
86+ Err(error.to_string())
87+ }
88+ }
89+}
90+
91+/// Installs what is downloaded, if anything. On Windows the installer
92+/// ends this process itself; elsewhere the caller goes on.
93+fn install(app: &AppHandle) -> bool {
94+ let ready = app.state::<config::State>().update.lock().unwrap().take();
95+ let Some(ready) = ready else { return false };
96+ match ready.update.install(ready.bytes) {
97+ Ok(()) => true,
98+ Err(error) => {
99+ tell(app, "The update could not be installed", &error.to_string());
100+ false
101+ }
102+ }
103+}
104+
105+/// The tray's restart: installs the update and starts the new version.
106+pub fn restart(app: &AppHandle) {
107+ if install(app) {
108+ app.restart();
109+ }
110+}
111+
112+/// Quit, installing a downloaded update on the way out.
113+pub fn quit(app: &AppHandle) {
114+ install(app);
115+ app.exit(0);
116+}
+53−0
1+{
2+ "$schema": "https://schema.tauri.app/config/2",
3+ "productName": "g1t",
4+ "identifier": "sh.g1t.desktop",
5+ "build": {
6+ "frontendDist": "../launcher"
7+ },
8+ "app": {
9+ "withGlobalTauri": true,
10+ "windows": [],
11+ "security": {
12+ "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
13+ }
14+ },
15+ "bundle": {
16+ "active": true,
17+ "targets": "all",
18+ "createUpdaterArtifacts": true,
19+ "icon": [
20+ "icons/32x32.png",
21+ "icons/128x128.png",
22+ "icons/128x128@2x.png",
23+ "icons/icon.icns",
24+ "icons/icon.ico"
25+ ],
26+ "category": "DeveloperTool",
27+ "shortDescription": "g1t on your desktop",
28+ "longDescription": "g1t in a window of its own: chat, agents, artifacts and code, with notifications from your system, a badge on the icon, g1t:// links and updates that install themselves.",
29+ "publisher": "Flagon, Inc.",
30+ "copyright": "Flagon, Inc.",
31+ "homepage": "https://g1t.sh",
32+ "windows": {
33+ "webviewInstallMode": { "type": "downloadBootstrapper" },
34+ "nsis": {
35+ "installMode": "currentUser",
36+ "languages": ["English"]
37+ }
38+ },
39+ "macOS": {
40+ "minimumSystemVersion": "11.0"
41+ }
42+ },
43+ "plugins": {
44+ "updater": {
45+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDMEU2MjkxRkU5N0RBRUMKUldUczJwZitrV0lPZkZVckJFeXdhMUMyN2s3a2VoZ2VYRkZESFBhQkY5UnBuS3RhbkNIbEU3bVMK",
46+ "endpoints": ["https://g1t.sh/downloads/desktop/latest.json"],
47+ "windows": { "installMode": "passive" }
48+ },
49+ "deep-link": {
50+ "desktop": { "schemes": ["g1t"] }
51+ }
52+ }
53+}
+1−0
8181 { label: 'How g1t works', slug: 'concepts/overview' },
8282 { label: 'Home', slug: 'guides/home' },
8383 { label: 'Notifications', slug: 'guides/notifications' },
84+ { label: 'The desktop app', slug: 'guides/desktop' },
8485 { label: 'Search and Explore', slug: 'guides/search' },
8586 { label: 'Status and incidents', slug: 'guides/status' },
8687 ],
+5−3
679679 | **Volume** | From 0 to 100. | 60 |
680680 | **New message**, **Direct message**, **Mention**, **Agent finished**, **Sent** | Each sound on or off. | All on but Sent |
681681 | **Do not disturb** | Off, or for 30 minutes, an hour, two hours, or until tomorrow at 9:00 your time. | Off |
682−| **Desktop notifications** | While this window is behind another, a message to you shows as a system notification from the open tab. Turning it on asks the browser's permission once, from this switch and never when a page loads. Not offered where browser notifications above are on: those already show it. | Off |
682+| **Desktop notifications** | While this window is behind another, a message to you shows as a system notification from the open tab. Turning it on asks the browser's permission once, from this switch and never when a page loads. Not offered where browser notifications above are on: those already show it. In [the desktop app](/guides/desktop/) it is on, and the app shows them itself. | Off |
683683
684684 **Do not disturb** is one thing everywhere: the bell-off button at the top
685685 of the chat sidebar, **Pause notifications** on the menu on your avatar,
726726 Messages in [site-wide search](/guides/search/), with only what you can
727727 read. <Soon />
728728 </Card>
729− <Card title="Desktop and mobile apps" icon="smartphone">
730− Installable apps with notifications. <Soon />
729+ <Card title="Desktop app" icon="monitor">
730+ g1t in a window of its own, with notifications from your system and a
731+ badge on the icon: [the desktop app](/guides/desktop/). A phone app is
732+ coming.
731733 </Card>
732734 </CardGrid>
733735
+137−0
1+---
2+title: The desktop app
3+description: g1t in a window of its own, for Windows, macOS and Linux, with notifications from your system, a badge on the icon, g1t:// links, and updates that install themselves.
4+---
5+
6+The desktop app is g1t in a window of its own. Everything in it is the
7+site, [g1t.sh](https://g1t.sh), loaded live: chat, agents, artifacts and
8+code, exactly as in a browser, so every change to g1t reaches the app the
9+moment it is deployed. What the app adds is what a browser cannot do:
10+
11+- **Notifications from your system**, for a message to you, a mention or
12+ an agent finishing something, while the window is behind another.
13+- **A badge on the icon**, with what is waiting for you, and a tray icon
14+ that keeps g1t running when the window is closed.
15+- **`g1t://` links**, which open a page in the app from anywhere on your
16+ computer.
17+- **Updates that install themselves**, signed by g1t, in the background.
18+- **Your own g1t**, if you [run one](/guides/self-hosting/): the app points
19+ at any server.
20+
21+Get it at [g1t.sh/download](https://g1t.sh/download), or from your account
22+menu, **Get the desktop app**.
23+
24+## Install
25+
26+| Platform | What you get | Notes |
27+| --- | --- | --- |
28+| **Windows** 10 and 11 | An installer, `g1t-<version>-windows-x64-setup.exe` | Installs for your account alone; no administrator needed. Uses the WebView2 runtime Windows already has, and fetches it if a machine lacks it. |
29+| **macOS** 11 and later | A disk image, Apple silicon or Intel | Drag g1t to Applications. The app is not yet notarized with Apple: the first time, right-click it and choose **Open**. |
30+| **Linux** | An AppImage, a `.deb` or an `.rpm` | The AppImage runs anywhere once made executable (`chmod +x`). It needs WebKitGTK 4.1, which the packages install for you. |
31+
32+Every file's SHA-256 is in `SHA256SUMS` beside it, at
33+`https://g1t.sh/downloads/desktop/<version>/`.
34+
35+The app opens on g1t.sh and asks you to sign in once; it keeps you signed
36+in like a browser does. Signing in with GitHub goes to github.com and comes
37+back inside the window.
38+
39+## Notifications and the badge
40+
41+Inside the app, the **Desktop notifications** switch in
42+[Settings → Notifications](/guides/notifications/#settings) is on and the
43+app shows them itself: while the window is behind another or hidden, a
44+message to you shows as a notification from your system, under the same
45+rules as in a browser: [what you chose to be told about](/guides/chat/#notifications),
46+never while [Do not disturb](/guides/chat/#sounds-and-do-not-disturb) is
47+on, never for a muted conversation. With the window in front, the pop-up
48+in the corner shows instead, as on the site. Clicking a notification
49+brings the app forward; opening the conversation it is about from the
50+notification is coming.
51+
52+The icon carries a badge with what is waiting for you: the count on macOS
53+and Linux, a lavender dot on Windows. The tray icon's hover text says the
54+number everywhere.
55+
56+[Chat sounds](/guides/chat/#sounds-and-do-not-disturb) play as on the
57+site, through the app's own audio.
58+
59+## The window and the tray
60+
61+Closing the window hides it; g1t keeps running in the tray (the menu bar
62+on macOS) so notifications still arrive. Click the tray icon, or its
63+**Open g1t**, to bring the window back; on macOS the dock icon does too.
64+**Quit g1t** in the tray menu ends it. The window remembers its size and
65+place.
66+
67+The tray menu also holds **Check for updates…**, **Use another g1t…** and,
68+when an update is downloaded, **Restart to update**.
69+
70+## Links
71+
72+A **`g1t://` link** opens a page in the app: `g1t://acme/-/chat/dm/chn_1`
73+is `g1t.sh/acme/-/chat/dm/chn_1`. The installer registers the scheme; a
74+link opened while the app is running moves it to the page without a
75+reload, and one opened while it is not starts it there. Scripts, launchers
76+and other apps on your computer can use it.
77+
78+A link to another site opens in the browser your system uses; the window
79+stays on g1t. Links on g1t that would open a new tab open in the window
80+instead, as it has no tabs. Signing in with GitHub is the one trip
81+outside g1t that stays in the window.
82+
83+## Updates
84+
85+The app asks `g1t.sh/downloads/desktop/latest.json` for a newer version
86+shortly after it starts and every six hours, and downloads one in the
87+background. It never installs under you: a notification and the tray's
88+**Restart to update** say it is ready, and the restart, or quitting,
89+installs it. **Check for updates…** in the tray asks now and tells you the
90+answer.
91+
92+Every release is signed with g1t's key, whose public half is built into
93+the app; a download whose signature does not check out is thrown away.
94+Because the window shows the site itself, most changes to g1t need no
95+update at all: the app updates when something native changes.
96+
97+## Your own g1t
98+
99+If you [run g1t yourself](/guides/self-hosting/), point the app at it:
100+**Use another g1t…** in the tray menu, then your server's address, such as
101+`https://g1t.example.com`. The app keeps it and opens there from then on;
102+**Use g1t.sh** on the same page goes back. Only that server's pages can
103+talk to the app, whichever it is, and updates still come from g1t.sh.
104+
105+## What's not there yet
106+
107+- **A runner inside the app**, so an agent can work on your own machine
108+ with what only it reaches, as [the runners page](/guides/self-hosted-runners/)
109+ says. The self-hosted runner works on the same machine meanwhile.
110+- **Opening the conversation from a notification.** Clicking one brings
111+ the app forward; the page it is about is coming.
112+- **A page of its own while offline.** If the network goes away while a
113+ page is open, the window shows the web view's own error until it is
114+ back; starting without a network shows the app's own page, with a retry.
115+- **Notarization on macOS**, so the first open needs no right-click.
116+- **A phone app.**
117+
118+## For operators: releasing the app
119+
120+The app lives in `apps/desktop` in [g1t's repository](https://g1t.sh/flagon-io/g1t):
121+a Tauri shell in Rust under `src-tauri`, and a launcher page it bundles.
122+A release is a tag `desktop-v<version>`, where the version is the one in
123+`apps/desktop/src-tauri/Cargo.toml`: push it, and the `Desktop release`
124+workflow builds the installers, signs the updater's files, publishes the
125+release to the `g1t-downloads` bucket and points `latest.json` at it.
126+`scripts/desktop-release.mjs` does the same by hand, and its header lists
127+the files a release holds.
128+
129+| What | Where |
130+| --- | --- |
131+| The updater's signing key | `node scripts/desktop-release.mjs keygen`, once. The private key is the repository secret `DESKTOP_SIGNING_KEY`; the public key is `plugins.updater.pubkey` in `tauri.conf.json`. Losing the private key means every installed app stops updating: keep it. |
132+| Windows and Linux builds | A Linux machine builds both: Linux natively, Windows with `cargo-xwin` and NSIS. |
133+| macOS builds | A Mac, registered as a [self-hosted runner](/guides/self-hosted-runners/) with the labels `self-hosted` and `macos`, and the repository variable `DESKTOP_MACOS_RUNNER` set to `true`. Without one, the macOS job is skipped and the release carries Windows and Linux. |
134+| Where releases are served | `g1t.sh/downloads/desktop/<version>/<file>`, from the same bucket as the runner's and the CLI's releases. |
135+
136+A self-hosted g1t does not serve the app's releases: the app always
137+updates from g1t.sh, and `g1t.sh/download` is where to send people.
+1−1
189189 | --- | --- | --- |
190190 | **Sounds**, **Sound set**, **Volume**, one switch per sound | What chat sounds like; see [Sounds and do not disturb](/guides/chat/#sounds-and-do-not-disturb). | On, Soft, 60, all but Sent |
191191 | **Do not disturb** | Silences sounds, pop-ups and browser notifications until a time you choose. | Off |
192−| **Desktop notifications** | A system notification from an open tab for a message to you while the window is behind another. | Off |
192+| **Desktop notifications** | A system notification from an open tab for a message to you while the window is behind another. In [the desktop app](/guides/desktop/) it is on, and the app shows them itself. | Off |
193193 | **Notify me about**, **Browser notifications** | What pops up and is pushed; see [chat notifications](/guides/chat/#notifications). | DMs and mentions; off |
194194 | **Email** | One checkbox per reason: you are also emailed when you are told of something for it. | agent waiting, review requested, mentioned |
195195 | **Repositories you create** | How you watch a new repository you create: **Participating and @mentions** or **All activity**. | All activity |
+2−1
562562 { text: "Agents that look up code, issues and checks as they answer", soon: true },
563563 { text: "Pull requests, checks and deploys as cards in the channel", soon: true },
564564 { text: "Mentions and approvals in Notifications", soon: true },
565− { text: "Desktop and mobile apps", soon: true },
565+ "A desktop app for Windows, macOS and Linux",
566+ { text: "A phone app", soon: true },
566567 ]}
567568 />
568569 <p className="mt-5 flex max-w-xl flex-wrap items-center gap-x-2.5 gap-y-1.5 text-sm leading-6 text-muted">
+11−1
33
44 import type { SoundSettings } from "@g1t/contracts/sounds";
55
6−import { applyAttention, refreshPush, reportInbox, setNavigator, setViewing, startFeed, useLiveBadges } from "../../lib/notify-client";
6+import { applyAttention, desktopBridge, refreshPush, reportInbox, setNavigator, setViewing, startFeed, useLiveBadges } from "../../lib/notify-client";
77 import { setSoundViewer } from "../../lib/sound-events";
88 import { applySoundSettings, startSounds } from "../../lib/sounds";
99 import { Toaster } from "./toaster";
4747
4848 useEffect(() => startFeed(workspace), [workspace]);
4949
50+ // Inside the desktop app, a g1t:// link names a page here; the app hands
51+ // the path over and the page moves without a reload.
52+ useEffect(() => {
53+ const bridge = desktopBridge();
54+ if (!bridge?.onNavigate) return;
55+ return bridge.onNavigate((href) => {
56+ if (typeof href === "string" && href.startsWith("/")) navigate(href);
57+ });
58+ }, [navigate]);
59+
5060 useEffect(() => setViewing({ path: pathname + search }), [pathname, search]);
5161
5262 // The inbox as the page just read it (after marking items, say).
+6−3
2727 { set: "bright", label: "Bright", detail: "Higher and shorter." },
2828 ];
2929
30−type Desktop = "on" | "off" | "blocked" | "unsupported" | "pushed";
30+type Desktop = "on" | "off" | "blocked" | "unsupported" | "pushed" | "app";
3131
3232 function desktopState(settings: SoundSettings): Desktop {
33+ // The desktop app shows them itself, whatever the switch says.
34+ if (typeof window !== "undefined" && window.g1tDesktop) return "app";
3335 if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
3436 if (permission() === "denied") return "blocked";
3537 if (pushChoice() === "on" && permission() === "granted") return "pushed";
4244 blocked: "Blocked by your browser. Allow notifications for this site in its settings, then come back.",
4345 unsupported: "This browser can't show system notifications.",
4446 pushed: "Browser notifications above already show a message to you whenever no tab is in front.",
47+ app: "On. The desktop app shows a message to you as a notification from your system while its window is behind another.",
4548 };
4649
4750 /**
103106 };
104107
105108 const off = !settings.sounds_enabled;
106− const desktopLocked = desktop === "blocked" || desktop === "unsupported" || desktop === "pushed";
109+ const desktopLocked = desktop === "blocked" || desktop === "unsupported" || desktop === "pushed" || desktop === "app";
107110
108111 return (
109112 <section aria-labelledby="sounds-heading" className="space-y-4">
252255 </p>
253256 </div>
254257 <Switch
255− checked={desktop === "on"}
258+ checked={desktop === "on" || desktop === "app"}
256259 disabled={desktopLocked || busy}
257260 onCheckedChange={(on) => void toggleDesktop(on)}
258261 aria-label="Desktop notifications"
+12−1
1−import { Activity, BarChart3, Building2, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Bot, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Coins, Compass, CreditCard, Fingerprint, GanttChart, Gauge, GitBranch, GitPullRequest, Globe, History, KanbanSquare, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, ListTree, Lock, LogOut, Mail, Network, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Store, House, Ticket, TrendingUp, UserRoundKey, Users, UsersRound, Webhook, X, ArrowLeftRight } from "lucide-react";
1+import { Activity, BarChart3, Building2, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Bot, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Coins, Compass, CreditCard, Fingerprint, GanttChart, Gauge, GitBranch, GitPullRequest, Globe, History, KanbanSquare, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, ListTree, Lock, LogOut, Mail, MonitorDown, Network, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Store, House, Ticket, TrendingUp, UserRoundKey, Users, UsersRound, Webhook, X, ArrowLeftRight } from "lucide-react";
22 import { Fragment, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
33 import { Link, useFetcher, useLocation, useNavigation, useRouteLoaderData, useSubmit } from "react-router";
44
77 import { InMain } from "./landmark";
88 import { CommandPalette, type PaletteCommand, PaletteKey, usePaletteShortcut } from "./command-palette";
99 import { NotificationsBell } from "./inbox";
10+import { useDesktopApp } from "../lib/notify-client";
1011 import { SpendPill } from "./spend";
1112 import { PinButton } from "./pin-button";
1213 import { Hint } from "./ui/hint";
380381 const submit = useSubmit();
381382 const [open, setOpen] = useState(false);
382383 const [keys, setKeys] = useState(false);
384+ // Inside the desktop app there is nothing to get.
385+ const inApp = useDesktopApp();
383386 // Your status's dialog: outside the menu, so the menu closes behind it.
384387 const [editing, setEditing] = useState(false);
385388 // Name, primary address and invites left: asked for once, as soon as the
501504 Keyboard shortcuts
502505 <PaletteKey className="ml-auto font-sans text-xs text-faint" />
503506 </DropdownMenuItem>
507+ {!inApp && (
508+ <DropdownMenuItem asChild className={MENU_ROW}>
509+ <Link to="/download">
510+ <MonitorDown />
511+ Get the desktop app
512+ </Link>
513+ </DropdownMenuItem>
514+ )}
504515 </DropdownMenuGroup>
505516 <DropdownMenuSeparator className="my-1.5" />
506517 {/* Submitted from here: the menu closes on select, and a button
+23−9
4646 // ── The desktop bridge ───────────────────────────────────────────────────
4747
4848 /**
49− * What the desktop app (an Electron shell loading this web app) exposes
50− * from its preload script with
51− * `contextBridge.exposeInMainWorld("g1tDesktop", …)`. When it is there,
52− * notifications go to it instead of the browser:
49+ * What the desktop app (apps/desktop, a Tauri shell loading this site)
50+ * puts at `window.g1tDesktop` from a script it runs at the start of every
51+ * page (src/bridge.js there). When it is there, notifications go to it
52+ * instead of the browser, the badge to the app's icon, and links to other
53+ * sites to the system's browser:
5354 *
5455 * ```ts
5556 * window.g1tDesktop = {
56− * version: "1.0.0",
57− * // A native notification. Clicking it brings the window forward and
58− * // navigates to `href` (calling the `onNavigate` listeners).
57+ * version: "0.1.0",
58+ * platform: "windows" | "macos" | "linux",
59+ * // A notification from the system, for a message to the person while
60+ * // the window is behind another. Clicking it brings the app forward;
61+ * // it does not yet open `href`.
5962 * notify({ id, title, body, href, tag, icon }) {},
60− * // The dock or taskbar badge and the tray's count; 0 clears it.
63+ * // What needs the person, on the app's icon and in the tray; 0 clears it.
6164 * setBadge(count) {},
6265 * // Opens a URL outside the app, in the system browser.
6366 * openUrl(url) {},
64− * // g1t:// deep links and notification clicks, as site paths ("/acme/-/chat/dm/chn_1").
67+ * // g1t:// links, as site paths ("/acme/-/chat/dm/chn_1").
6568 * onNavigate(listener) { return () => {} },
69+ * // Looks for an update now: "current", "ready:<version>", or rejects with why.
70+ * checkForUpdates() { return Promise.resolve("current") },
6671 * };
6772 * ```
6873 */
6974 export type DesktopBridge = {
7075 version?: string;
76+ platform?: string;
7177 notify(notification: { id: string; title: string; body: string; href: string; tag: string; icon?: string | null }): void;
7278 setBadge(count: number): void;
7379 openUrl(url: string): void;
7480 onNavigate?(listener: (href: string) => void): () => void;
81+ checkForUpdates?(): Promise<string>;
7582 };
7683
7784 declare global {
8693 return bridge && typeof bridge.notify === "function" && typeof bridge.setBadge === "function" ? bridge : null;
8794 }
8895
96+const never = () => () => {};
97+
98+/** Whether this page is inside the desktop app: false on the server, and until hydrated. */
99+export function useDesktopApp(): boolean {
100+ return useSyncExternalStore(never, () => !!desktopBridge(), () => false);
101+}
102+
89103 // ── Sinks ────────────────────────────────────────────────────────────────
90104
91105 /** Where a live notification goes once it arrives. */
+4−1
3535 `img-src 'self' https: data: blob:${files}`,
3636 `media-src 'self' https:${files}`,
3737 "font-src 'self' data:",
38− "connect-src 'self' https:",
38+ // `ipc:` and ipc.localhost are the desktop app's own channel to its
39+ // shell, which the page calls through window.g1tDesktop
40+ // (lib/notify-client.ts); no browser resolves either.
41+ "connect-src 'self' https: ipc: http://ipc.localhost",
3942 "frame-src 'none'",
4043 "object-src 'none'",
4144 "base-uri 'self'",
+2−0
112112 */
113113 export function showDesktopToast(notification: FeedNotification, open: (href: string) => void): void {
114114 try {
115+ // Inside the desktop app the shell shows it (lib/notify-client.ts, desktopSink).
116+ if (typeof window !== "undefined" && window.g1tDesktop) return;
115117 if (typeof Notification === "undefined" || Notification.permission !== "granted") return;
116118 const shown = new Notification(notification.title, {
117119 body: notification.body,
+3−1
8787 // For search engines: what may be crawled, and a map of the public pages.
8888 route("robots.txt", "routes/robots-txt.ts"),
8989 route("sitemap.xml", "routes/sitemap-xml.ts"),
90− // Releases of the self-hosted runner and the g1t CLI, from R2.
90+ // The desktop app: which to install, from its newest release.
91+ route("download", "routes/download.tsx"),
92+ // Releases of the desktop app, the self-hosted runner and the g1t CLI, from R2.
9193 route("downloads/:tool/*", "routes/downloads-runner.ts"),
9294 // A workspace's own pages sit under `-`, which no repository can be named.
9395 route(":owner", "routes/workspace/layout.tsx", [
+219−0
1+import { env } from "cloudflare:workers";
2+import { Apple, Bell, Download, Link2, Monitor, RefreshCw, Server, Terminal } from "lucide-react";
3+import type { ReactNode } from "react";
4+import { useLoaderData } from "react-router";
5+
6+import type { Route } from "./+types/download";
7+import { TrustPage } from "../components/trust-page";
8+import { ButtonLink } from "../components/ui";
9+import { Card } from "../components/ui/card";
10+import { page } from "../lib/meta";
11+
12+/**
13+ * g1t.sh/download: the desktop app, from its newest release. The release
14+ * is `desktop/latest.json` in the g1t-downloads bucket, as
15+ * scripts/desktop-release.mjs writes it: what the app's updater reads
16+ * (`platforms`), and the list this page shows (`downloads`). The visitor's
17+ * own platform, read from the browser, comes first.
18+ */
19+
20+export type DesktopDownload = {
21+ platform: "windows" | "macos" | "linux";
22+ arch: "x64" | "arm64";
23+ kind: string;
24+ name: string;
25+ size: number;
26+ sha256: string;
27+};
28+
29+export type DesktopRelease = {
30+ version: string;
31+ pub_date: string;
32+ notes?: string;
33+ downloads: DesktopDownload[];
34+};
35+
36+type Platform = DesktopDownload["platform"];
37+
38+/** Which platform a browser says it is on; null when it does not say. */
39+export function platformOf(userAgent: string | null): Platform | null {
40+ if (!userAgent) return null;
41+ if (/Windows/i.test(userAgent)) return "windows";
42+ if (/Mac OS X|Macintosh/i.test(userAgent)) return "macos";
43+ if (/Linux|X11/i.test(userAgent) && !/Android/i.test(userAgent)) return "linux";
44+ return null;
45+}
46+
47+export function meta(args: Route.MetaArgs) {
48+ return page(args, {
49+ title: "Download g1t for your desktop",
50+ description: "g1t in a window of its own, for Windows, macOS and Linux: notifications from your system, a badge on the icon, g1t:// links, and updates that install themselves.",
51+ });
52+}
53+
54+export async function loader({ request }: Route.LoaderArgs) {
55+ const bucket = env.DOWNLOADS;
56+ let release: DesktopRelease | null = null;
57+ try {
58+ const found = bucket ? await bucket.get("desktop/latest.json") : null;
59+ if (found) {
60+ const read = (await found.json()) as Partial<DesktopRelease>;
61+ if (read && typeof read.version === "string" && Array.isArray(read.downloads)) {
62+ release = { version: read.version, pub_date: read.pub_date ?? "", notes: read.notes, downloads: read.downloads };
63+ }
64+ }
65+ } catch (error) {
66+ console.error("download: the release could not be read", error);
67+ }
68+ return { release, platform: platformOf(request.headers.get("user-agent")) };
69+}
70+
71+const PLATFORMS: { id: Platform; name: string; icon: ReactNode; note: string }[] = [
72+ { id: "windows", name: "Windows", icon: <Monitor size={16} />, note: "Windows 10 and 11. Installs for you alone, no administrator needed." },
73+ { id: "macos", name: "macOS", icon: <Apple size={16} />, note: "macOS 11 and later. Apple silicon and Intel are separate downloads." },
74+ { id: "linux", name: "Linux", icon: <Terminal size={16} />, note: "An AppImage that runs anywhere, or a package for Debian and Ubuntu." },
75+];
76+
77+const KINDS: Record<string, string> = {
78+ installer: "Installer",
79+ dmg: "Disk image",
80+ appimage: "AppImage",
81+ deb: "Debian package",
82+ rpm: "RPM package",
83+};
84+
85+function archLabel(download: DesktopDownload): string {
86+ if (download.platform === "macos") return download.arch === "arm64" ? "Apple silicon" : "Intel";
87+ return download.arch === "arm64" ? "arm64" : "x64";
88+}
89+
90+function size(bytes: number): string {
91+ if (!bytes) return "";
92+ return bytes >= 1024 * 1024 ? `${(bytes / (1024 * 1024)).toFixed(0)} MB` : `${Math.ceil(bytes / 1024)} KB`;
93+}
94+
95+const WHAT: { icon: ReactNode; title: string; text: string }[] = [
96+ { icon: <Bell size={16} />, title: "Notifications from your system", text: "A message to you, a mention, or an agent finished, while the window is behind another; the icon carries a badge until you have looked." },
97+ { icon: <Link2 size={16} />, title: "g1t:// links", text: "A g1t:// link opens the page in the app, from anywhere on your computer. Links to other sites open in your browser." },
98+ { icon: <RefreshCw size={16} />, title: "Updates install themselves", text: "The app downloads a new version in the background, signed by g1t, and asks only to restart. Everything you see is the site itself, so most changes need no update at all." },
99+ { icon: <Server size={16} />, title: "Your own g1t", text: "Running g1t yourself? The tray's \u201cUse another g1t\u201d points the app at your server." },
100+];
101+
102+export default function DownloadPage() {
103+ const { release, platform } = useLoaderData<typeof loader>();
104+ const ordered = platform ? [...PLATFORMS.filter((p) => p.id === platform), ...PLATFORMS.filter((p) => p.id !== platform)] : PLATFORMS;
105+ const yours = platform && release ? release.downloads.filter((d) => d.platform === platform) : [];
106+ const featured = yours.find((d) => d.kind === "installer" || d.kind === "dmg" || d.kind === "appimage") ?? yours[0] ?? null;
107+ const href = (download: DesktopDownload) => `/downloads/desktop/${release!.version}/${download.name}`;
108+
109+ return (
110+ <TrustPage
111+ eyebrow="Desktop app"
112+ title="g1t on your desktop"
113+ lede={
114+ <p>
115+ The whole of g1t in a window of its own: chat, agents, artifacts and code, for Windows, macOS and Linux.
116+ {release && (
117+ <>
118+ {" "}
119+ Version {release.version}
120+ {release.pub_date && (
121+ <>
122+ , released{" "}
123+ {new Date(release.pub_date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}
124+ </>
125+ )}
126+ .
127+ </>
128+ )}
129+ </p>
130+ }
131+ >
132+ <div className="max-w-3xl">
133+ {!release && (
134+ <Card className="p-5">
135+ <p className="font-medium">The first release is on its way.</p>
136+ <p className="mt-1 text-sm text-muted">
137+ The desktop app is built and being released now. Until it is here, g1t works as an installed web app too: in your browser's menu, choose
138+ Install or Add to Dock.
139+ </p>
140+ </Card>
141+ )}
142+
143+ {release && featured && (
144+ <Card className="flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between">
145+ <div>
146+ <p className="font-medium">For your {PLATFORMS.find((p) => p.id === platform)?.name}</p>
147+ <p className="mt-1 text-sm text-muted">
148+ {KINDS[featured.kind] ?? featured.kind}, {archLabel(featured)}
149+ {featured.size ? `, ${size(featured.size)}` : ""}.
150+ {yours.length > 1 ? " The other downloads for it are below." : ""}
151+ </p>
152+ </div>
153+ <ButtonLink to={href(featured)} reloadDocument className="shrink-0">
154+ <Download size={16} />
155+ Download g1t {release.version}
156+ </ButtonLink>
157+ </Card>
158+ )}
159+
160+ <div className="mt-10 grid gap-3 sm:grid-cols-2">
161+ {WHAT.map((item) => (
162+ <div key={item.title} className="flex gap-3 rounded-xl border border-line bg-surface p-5">
163+ <span className="mt-0.5 shrink-0 text-accent">{item.icon}</span>
164+ <span>
165+ <span className="block font-medium">{item.title}</span>
166+ <span className="mt-1 block text-sm text-muted">{item.text}</span>
167+ </span>
168+ </div>
169+ ))}
170+ </div>
171+
172+ {release && (
173+ <section className="mt-14">
174+ <h2 className="text-xl font-semibold tracking-tight">All downloads</h2>
175+ <p className="mt-1 text-sm text-muted">
176+ Every file of version {release.version}, with its SHA-256 in{" "}
177+ <a href={`/downloads/desktop/${release.version}/SHA256SUMS`} className="text-accent hover:underline">
178+ SHA256SUMS
179+ </a>
180+ .
181+ </p>
182+ <div className="mt-4 space-y-3">
183+ {ordered.map((p) => {
184+ const files = release.downloads.filter((d) => d.platform === p.id);
185+ if (files.length === 0) return null;
186+ return (
187+ <Card key={p.id} tone="plain" divided className="rounded-xl">
188+ <div className="flex items-center gap-2 px-4 py-3">
189+ <span className="text-accent">{p.icon}</span>
190+ <span className="font-medium">{p.name}</span>
191+ <span className="text-sm text-muted">· {p.note}</span>
192+ </div>
193+ {files.map((d) => (
194+ <div key={d.name} className="flex flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2.5 text-sm">
195+ <a href={href(d)} className="font-medium text-fg hover:underline">
196+ {KINDS[d.kind] ?? d.kind}, {archLabel(d)}
197+ </a>
198+ <span className="text-muted">{size(d.size)}</span>
199+ <code className="ml-auto truncate font-mono text-xs text-faint">{d.name}</code>
200+ </div>
201+ ))}
202+ </Card>
203+ );
204+ })}
205+ </div>
206+ <p className="mt-6 text-sm text-muted">
207+ On macOS the app is not yet notarized with Apple: the first time, right-click it and choose Open. How the app works, and how a
208+ release is made, is in{" "}
209+ <a href="https://docs.g1t.sh/guides/desktop/" className="text-accent hover:underline">
210+ the desktop app guide
211+ </a>
212+ .
213+ </p>
214+ </section>
215+ )}
216+ </div>
217+ </TrustPage>
218+ );
219+}
+7−5
55 /**
66 * Releases: `g1t.sh/downloads/<tool>/<version>/<file>`, served from the
77 * g1t-downloads R2 bucket. `runner` is the self-hosted runner
8− * (scripts/runner-release.mjs), `cli` the g1t CLI (scripts/cli-release.mjs).
9− * `latest/<file>` is the newest release's, as its `latest.json` names it;
10− * the runner's `latest.json` and `latest.json.sig` are what runners check
11− * before updating.
8+ * (scripts/runner-release.mjs), `cli` the g1t CLI (scripts/cli-release.mjs),
9+ * `desktop` the desktop app (scripts/desktop-release.mjs). `latest/<file>`
10+ * is the newest release's, as its `latest.json` names it; the runner's
11+ * `latest.json` and `latest.json.sig` are what runners check before
12+ * updating, and the desktop app's `latest.json` names each platform's
13+ * installer with its signature, as the app's updater reads it.
1214 */
13−const TOOLS = new Set(["runner", "cli"]);
15+const TOOLS = new Set(["runner", "cli", "desktop"]);
1416 const TYPES: Record<string, string> = {
1517 json: "application/json",
1618 sig: "text/plain; charset=utf-8",
+1−1
352352 // served from g1t.sh instead.
353353 "secrets": ["USERCONTENT_KEY"],
354354 "setup": [
355− "The R2 bucket g1t-downloads, for the self-hosted runner's releases: npx wrangler r2 bucket create g1t-downloads",
355+ "The R2 bucket g1t-downloads, for the releases of the self-hosted runner, the CLI and the desktop app: npx wrangler r2 bucket create g1t-downloads",
356356 "The zone g1tusercontent.com on the account; the Worker's custom domain on it is made by the deploy",
357357 "The key for private files' addresses: `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\" | npx wrangler secret put USERCONTENT_KEY` in apps/web"
358358 ],
+242−0
1818 "node": ">=22.22.0"
1919 }
2020 },
21+ "apps/desktop": {
22+ "name": "@g1t/desktop",
23+ "devDependencies": {
24+ "@tauri-apps/cli": "^2.12.2"
25+ }
26+ },
2127 "apps/docs": {
2228 "name": "@g1t/docs",
2329 "version": "0.1.0",
19841990 "resolved": "services/deployments",
19851991 "link": true
19861992 },
1993+ "node_modules/@g1t/desktop": {
1994+ "resolved": "apps/desktop",
1995+ "link": true
1996+ },
19871997 "node_modules/@g1t/docs": {
19881998 "resolved": "apps/docs",
19891999 "link": true
52245234 "vite": "^5.2.0 || ^6 || ^7 || ^8"
52255235 }
52265236 },
5237+ "node_modules/@tauri-apps/cli": {
5238+ "version": "2.12.2",
5239+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.12.2.tgz",
5240+ "integrity": "sha512-PMSppzAnlT+BvZtRKuidxjRSm2r5t8kDpJ/czmmQUa0AtDNTwrjLE8vApK3phCWGg7isfnYL46Dl1jB4ArI0hA==",
5241+ "dev": true,
5242+ "license": "Apache-2.0 OR MIT",
5243+ "bin": {
5244+ "tauri": "tauri.js"
5245+ },
5246+ "engines": {
5247+ "node": ">= 10"
5248+ },
5249+ "funding": {
5250+ "type": "opencollective",
5251+ "url": "https://opencollective.com/tauri"
5252+ },
5253+ "optionalDependencies": {
5254+ "@tauri-apps/cli-darwin-arm64": "2.12.2",
5255+ "@tauri-apps/cli-darwin-x64": "2.12.2",
5256+ "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.2",
5257+ "@tauri-apps/cli-linux-arm64-gnu": "2.12.2",
5258+ "@tauri-apps/cli-linux-arm64-musl": "2.12.2",
5259+ "@tauri-apps/cli-linux-riscv64-gnu": "2.12.2",
5260+ "@tauri-apps/cli-linux-x64-gnu": "2.12.2",
5261+ "@tauri-apps/cli-linux-x64-musl": "2.12.2",
5262+ "@tauri-apps/cli-win32-arm64-msvc": "2.12.2",
5263+ "@tauri-apps/cli-win32-ia32-msvc": "2.12.2",
5264+ "@tauri-apps/cli-win32-x64-msvc": "2.12.2"
5265+ }
5266+ },
5267+ "node_modules/@tauri-apps/cli-darwin-arm64": {
5268+ "version": "2.12.2",
5269+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.12.2.tgz",
5270+ "integrity": "sha512-Lk3zaBTfzNvA7670Al5PNgo2M/95uaDPe1yp+ILJeDOmol87XiYuK5E5+FUbK5e6U6OORx9qpZdOo2U+5tETtQ==",
5271+ "cpu": [
5272+ "arm64"
5273+ ],
5274+ "dev": true,
5275+ "license": "Apache-2.0 OR MIT",
5276+ "optional": true,
5277+ "os": [
5278+ "darwin"
5279+ ],
5280+ "engines": {
5281+ "node": ">= 10"
5282+ }
5283+ },
5284+ "node_modules/@tauri-apps/cli-darwin-x64": {
5285+ "version": "2.12.2",
5286+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.12.2.tgz",
5287+ "integrity": "sha512-2CyYjJ833JB3sKRz4B0W/Tv4oOVC16ws1bxdSCWj1XvrmanWwvZi99e5tNIoCHwHhcOm0jh+sh0PCGQyls6Z+w==",
5288+ "cpu": [
5289+ "x64"
5290+ ],
5291+ "dev": true,
5292+ "license": "Apache-2.0 OR MIT",
5293+ "optional": true,
5294+ "os": [
5295+ "darwin"
5296+ ],
5297+ "engines": {
5298+ "node": ">= 10"
5299+ }
5300+ },
5301+ "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
5302+ "version": "2.12.2",
5303+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.12.2.tgz",
5304+ "integrity": "sha512-uvt2xbone4JftKarelssi86Kqnl7JiG1Smh5+Ae+nZmtM+JUhTGmqkcqfUPXRW8Ygwog92POZvd4oGDN9j8eXQ==",
5305+ "cpu": [
5306+ "arm"
5307+ ],
5308+ "dev": true,
5309+ "license": "Apache-2.0 OR MIT",
5310+ "optional": true,
5311+ "os": [
5312+ "linux"
5313+ ],
5314+ "engines": {
5315+ "node": ">= 10"
5316+ }
5317+ },
5318+ "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
5319+ "version": "2.12.2",
5320+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.12.2.tgz",
5321+ "integrity": "sha512-DZlDJ24u3tlocKOYqzR+b2M+Qr+5WFcoMBZ7EfdhHN5j3ZXwBBpms4G4gyNvPCrak5vMzmdZ65ecBXPJcC+5sA==",
5322+ "cpu": [
5323+ "arm64"
5324+ ],
5325+ "dev": true,
5326+ "libc": [
5327+ "glibc"
5328+ ],
5329+ "license": "Apache-2.0 OR MIT",
5330+ "optional": true,
5331+ "os": [
5332+ "linux"
5333+ ],
5334+ "engines": {
5335+ "node": ">= 10"
5336+ }
5337+ },
5338+ "node_modules/@tauri-apps/cli-linux-arm64-musl": {
5339+ "version": "2.12.2",
5340+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.12.2.tgz",
5341+ "integrity": "sha512-pb+RTZUYH/p0gR4j4ecCCq+FNfz3W3eKuUQvVEBn7MQZx6DzfLO7FaYzt2NyDKWpdNN3lP10mdzOvw3R5P5aAQ==",
5342+ "cpu": [
5343+ "arm64"
5344+ ],
5345+ "dev": true,
5346+ "libc": [
5347+ "musl"
5348+ ],
5349+ "license": "Apache-2.0 OR MIT",
5350+ "optional": true,
5351+ "os": [
5352+ "linux"
5353+ ],
5354+ "engines": {
5355+ "node": ">= 10"
5356+ }
5357+ },
5358+ "node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
5359+ "version": "2.12.2",
5360+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.12.2.tgz",
5361+ "integrity": "sha512-nidEvDUJyHYNbIv9MUk6KiEix8IXfRh4Wj2G2SA5g8nFmgbBCwazUNWYiG/LhNeNbfZpBMLerRBUtoQ5K0GidA==",
5362+ "cpu": [
5363+ "riscv64"
5364+ ],
5365+ "dev": true,
5366+ "libc": [
5367+ "glibc"
5368+ ],
5369+ "license": "Apache-2.0 OR MIT",
5370+ "optional": true,
5371+ "os": [
5372+ "linux"
5373+ ],
5374+ "engines": {
5375+ "node": ">= 10"
5376+ }
5377+ },
5378+ "node_modules/@tauri-apps/cli-linux-x64-gnu": {
5379+ "version": "2.12.2",
5380+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.12.2.tgz",
5381+ "integrity": "sha512-gkXyYn3w+Xfc8nz/DleEbkm1M7bBuU2cejqUXNmI98cA2P5kp+oPmS4ALTorb1FaNWjpsKoYZBWsbS9BXxYFkQ==",
5382+ "cpu": [
5383+ "x64"
5384+ ],
5385+ "dev": true,
5386+ "libc": [
5387+ "glibc"
5388+ ],
5389+ "license": "Apache-2.0 OR MIT",
5390+ "optional": true,
5391+ "os": [
5392+ "linux"
5393+ ],
5394+ "engines": {
5395+ "node": ">= 10"
5396+ }
5397+ },
5398+ "node_modules/@tauri-apps/cli-linux-x64-musl": {
5399+ "version": "2.12.2",
5400+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.12.2.tgz",
5401+ "integrity": "sha512-MrjgoZ09s8Hsn99XvxSNvK8NQTZxi7XiyK3FxxytzfHgz2jb3qUT69CZtsokRkN/zdi9rCgrMtq+w9XNICjcfQ==",
5402+ "cpu": [
5403+ "x64"
5404+ ],
5405+ "dev": true,
5406+ "libc": [
5407+ "musl"
5408+ ],
5409+ "license": "Apache-2.0 OR MIT",
5410+ "optional": true,
5411+ "os": [
5412+ "linux"
5413+ ],
5414+ "engines": {
5415+ "node": ">= 10"
5416+ }
5417+ },
5418+ "node_modules/@tauri-apps/cli-win32-arm64-msvc": {
5419+ "version": "2.12.2",
5420+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.12.2.tgz",
5421+ "integrity": "sha512-BR+CYM7iF3lkfyTtVkwMQU/s9hwegLpj3UqDjaxKCPmixQCa1csBBPy3C75V+FhLtOjIXNna4Gt6Vm9oxbV+qA==",
5422+ "cpu": [
5423+ "arm64"
5424+ ],
5425+ "dev": true,
5426+ "license": "Apache-2.0 OR MIT",
5427+ "optional": true,
5428+ "os": [
5429+ "win32"
5430+ ],
5431+ "engines": {
5432+ "node": ">= 10"
5433+ }
5434+ },
5435+ "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
5436+ "version": "2.12.2",
5437+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.12.2.tgz",
5438+ "integrity": "sha512-zODJo9l3TbNBM+GrO/yPqGeELNMK5cjIc+QHh1WQc3l8tD+jDZGTQyYuAxViCsM6Uh5uWJ//RnCZG9LXrvsXww==",
5439+ "cpu": [
5440+ "ia32"
5441+ ],
5442+ "dev": true,
5443+ "license": "Apache-2.0 OR MIT",
5444+ "optional": true,
5445+ "os": [
5446+ "win32"
5447+ ],
5448+ "engines": {
5449+ "node": ">= 10"
5450+ }
5451+ },
5452+ "node_modules/@tauri-apps/cli-win32-x64-msvc": {
5453+ "version": "2.12.2",
5454+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.12.2.tgz",
5455+ "integrity": "sha512-cQXI2ZaZmwBDt0XATCqASVanYizxkEE3ceQVlVHXaQ51HzsQUWA04c9Nz7LrrY2lgAu3jFMyat+8Yz3Z2R2qBA==",
5456+ "cpu": [
5457+ "x64"
5458+ ],
5459+ "dev": true,
5460+ "license": "Apache-2.0 OR MIT",
5461+ "optional": true,
5462+ "os": [
5463+ "win32"
5464+ ],
5465+ "engines": {
5466+ "node": ">= 10"
5467+ }
5468+ },
52275469 "node_modules/@tiptap/core": {
52285470 "version": "3.31.4",
52295471 "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.31.4.tgz",
+2−1
1414 "typecheck": "npm run typecheck --workspaces --if-present",
1515 "deploy": "npm run deploy -w @g1t/web",
1616 "test:deploy": "node --test \"scripts/deploy/*.test.mjs\"",
17− "test:ops": "node --test \"scripts/ops/*.test.mjs\""
17+ "test:ops": "node --test \"scripts/ops/*.test.mjs\"",
18+ "test:release": "node --test \"scripts/desktop-release.test.mjs\""
1819 },
1920 "devDependencies": {
2021 "typescript": "^5.9.3",
+2−0
2121 "v2",
2222 // g1t.sh/inbox, and the name it might also go by.
2323 "inbox", "notifications",
24+ // g1t.sh/download, the desktop app; g1t.sh/downloads/, the releases it and the runner come from.
25+ "download", "downloads",
2426 ]);
2527
2628 /** Whether `value`, whatever its case, is a name nobody can register: a route, or g1t's own. */
+271−0
1+#!/usr/bin/env node
2+// Releases of the desktop app (apps/desktop): built on each platform,
3+// signed by Tauri's updater key, collected into one folder, described in
4+// a manifest, and published to the g1t-downloads R2 bucket that g1t.sh
5+// serves at /downloads/desktop/ (apps/web/app/routes/downloads-runner.ts).
6+// The app checks `desktop/latest.json` for updates
7+// (apps/desktop/src-tauri/src/updates.rs); g1t.sh/download lists the same
8+// release (apps/web/app/routes/download.tsx).
9+//
10+// node scripts/desktop-release.mjs keygen # once: the updater's signing key
11+// node scripts/desktop-release.mjs build [--windows-from-linux]
12+// node scripts/desktop-release.mjs collect # this machine's bundles, into the release folder
13+// node scripts/desktop-release.mjs manifest [--notes "…"] # latest.json and SHA256SUMS from what is collected
14+// node scripts/desktop-release.mjs publish [--dry-run]
15+//
16+// What a release is, at desktop/<version>/ in the bucket:
17+//
18+// g1t-<version>-windows-x64-setup.exe (+ .sig) the Windows installer, and what the updater fetches
19+// g1t-<version>-macos-arm64.dmg, -macos-x64.dmg what people download on a Mac
20+// g1t-<version>-macos-arm64.app.tar.gz (+ .sig) what the updater fetches on a Mac
21+// g1t-<version>-linux-x64.AppImage (+ .sig) runs anywhere; what the updater fetches on Linux
22+// g1t-<version>-linux-x64.deb, .rpm packages
23+// SHA256SUMS `sha256 name`, one line each
24+// manifest.json { version, pub_date, notes, platforms, downloads }
25+//
26+// and at desktop/: latest.json, the newest release's manifest. `platforms`
27+// is what Tauri's updater reads: each platform's file and its signature,
28+// made with the private key as the bundles are built and checked against
29+// the public key in tauri.conf.json. `downloads` is what the download page
30+// lists: every file, with its platform, kind, size and SHA-256.
31+//
32+// Environment:
33+// TAURI_SIGNING_PRIVATE_KEY the updater's private key (keygen makes it): a g1t Actions secret
34+// TAURI_SIGNING_PRIVATE_KEY_PASSWORD its password; empty for a key made with --ci
35+// CLOUDFLARE_API_TOKEN for publish
36+
37+import { spawnSync } from "node:child_process";
38+import { createHash } from "node:crypto";
39+import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
40+import { basename, dirname, join } from "node:path";
41+import { fileURLToPath } from "node:url";
42+
43+const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
44+const APP = join(ROOT, "apps/desktop");
45+const TAURI = join(APP, "src-tauri");
46+const BUCKET = "g1t-downloads";
47+
48+/** What each platform's folder of bundles holds, and what the updater calls the platform. */
49+export const PLATFORMS = {
50+ "windows-x64": { triple: "x86_64-pc-windows-msvc", updater: "windows-x86_64" },
51+ "windows-arm64": { triple: "aarch64-pc-windows-msvc", updater: "windows-aarch64" },
52+ "macos-arm64": { triple: "aarch64-apple-darwin", updater: "darwin-aarch64" },
53+ "macos-x64": { triple: "x86_64-apple-darwin", updater: "darwin-x86_64" },
54+ "linux-x64": { triple: "x86_64-unknown-linux-gnu", updater: "linux-x86_64" },
55+ "linux-arm64": { triple: "aarch64-unknown-linux-gnu", updater: "linux-aarch64" },
56+};
57+
58+export function version() {
59+ const toml = readFileSync(join(TAURI, "Cargo.toml"), "utf8");
60+ const found = /^version\s*=\s*"([^"]+)"/m.exec(toml);
61+ if (!found) throw new Error("apps/desktop/src-tauri/Cargo.toml has no version");
62+ return found[1];
63+}
64+
65+const outDir = (v = version()) => join(ROOT, "target", "desktop-release", v);
66+export const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
67+
68+/** The platform this machine is, as releases name it. */
69+export function hostPlatform(platform = process.platform, arch = process.arch) {
70+ const os = platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux";
71+ return `${os}-${arch === "arm64" ? "arm64" : "x64"}`;
72+}
73+
74+/** The platform a Rust target triple builds for, or null. */
75+export function platformOfTriple(triple) {
76+ return Object.keys(PLATFORMS).find((key) => PLATFORMS[key].triple === triple) ?? null;
77+}
78+
79+/**
80+ * A bundle's place in a release: `{ platform, arch, kind, name, updater }`
81+ * for a file Tauri produced, or null for one that is not published (an
82+ * MSI, say). `name` is the file's name in the release, `updater` whether
83+ * the updater fetches this kind of file on that platform.
84+ */
85+export function classify(file, platform, v) {
86+ const [os, arch] = platform.split("-");
87+ const lower = file.toLowerCase();
88+ const named = (suffix) => `g1t-${v}-${platform}${suffix}`;
89+ if (lower.endsWith(".sig")) return null;
90+ if (os === "windows" && lower.endsWith("-setup.exe")) return { platform: os, arch, kind: "installer", name: named("-setup.exe"), updater: true };
91+ if (os === "macos" && lower.endsWith(".app.tar.gz")) return { platform: os, arch, kind: "update", name: named(".app.tar.gz"), updater: true };
92+ if (os === "macos" && lower.endsWith(".dmg")) return { platform: os, arch, kind: "dmg", name: named(".dmg"), updater: false };
93+ if (os === "linux" && lower.endsWith(".appimage")) return { platform: os, arch, kind: "appimage", name: named(".AppImage"), updater: true };
94+ if (os === "linux" && lower.endsWith(".deb")) return { platform: os, arch, kind: "deb", name: named(".deb"), updater: false };
95+ if (os === "linux" && lower.endsWith(".rpm")) return { platform: os, arch, kind: "rpm", name: named(".rpm"), updater: false };
96+ return null;
97+}
98+
99+/** The folders Tauri put bundles in, by the platform each is for. */
100+export function bundleDirs(target = join(TAURI, "target")) {
101+ const found = [];
102+ const host = join(target, "release", "bundle");
103+ if (existsSync(host)) found.push({ platform: hostPlatform(), dir: host });
104+ if (!existsSync(target)) return found;
105+ for (const entry of readdirSync(target)) {
106+ const platform = platformOfTriple(entry);
107+ const dir = join(target, entry, "release", "bundle");
108+ if (platform && existsSync(dir)) found.push({ platform, dir });
109+ }
110+ return found;
111+}
112+
113+/** Every file in a bundle folder, one level of kind folders down. */
114+function bundleFiles(dir) {
115+ const files = [];
116+ for (const kind of readdirSync(dir)) {
117+ const folder = join(dir, kind);
118+ if (!statSync(folder).isDirectory()) continue;
119+ for (const file of readdirSync(folder)) {
120+ if (statSync(join(folder, file)).isFile()) files.push(join(folder, file));
121+ }
122+ }
123+ return files;
124+}
125+
126+/**
127+ * The manifest of a release folder: Tauri's updater format (`platforms`),
128+ * plus `downloads` for the download page. `entries` are the collected
129+ * files' notes (collect.json), `signatures` each updater file's `.sig`.
130+ */
131+export function manifest(entries, v, { date = new Date().toISOString(), notes = "", base = `https://g1t.sh/downloads/desktop/${v}` } = {}) {
132+ const platforms = {};
133+ const downloads = [];
134+ for (const entry of entries) {
135+ if (entry.updater && entry.signature) {
136+ const key = PLATFORMS[`${entry.platform}-${entry.arch}`]?.updater;
137+ if (key) platforms[key] = { url: `${base}/${entry.name}`, signature: entry.signature };
138+ }
139+ if (entry.kind !== "update") {
140+ downloads.push({ platform: entry.platform, arch: entry.arch, kind: entry.kind, name: entry.name, size: entry.size, sha256: entry.sha256 });
141+ }
142+ }
143+ downloads.sort((a, b) => a.platform.localeCompare(b.platform) || a.arch.localeCompare(b.arch) || a.kind.localeCompare(b.kind));
144+ return { version: v, pub_date: date, notes, platforms, downloads };
145+}
146+
147+function run(command, args, options = {}) {
148+ const done = spawnSync(command, args, { stdio: "inherit", cwd: ROOT, shell: process.platform === "win32", ...options });
149+ if (done.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`);
150+}
151+
152+function keygen() {
153+ console.error("Making the updater's key pair with Tauri's signer. Keep the private key as the g1t Actions secret DESKTOP_SIGNING_KEY,");
154+ console.error("and put the public key in apps/desktop/src-tauri/tauri.conf.json (plugins.updater.pubkey).");
155+ run("npx", ["tauri", "signer", "generate", "--ci"], { cwd: APP });
156+}
157+
158+function build(windowsFromLinux) {
159+ if (!process.env.TAURI_SIGNING_PRIVATE_KEY) console.error("warning: TAURI_SIGNING_PRIVATE_KEY is not set; these bundles cannot be fetched by the updater");
160+ console.error(`building ${hostPlatform()}`);
161+ run("npx", ["tauri", "build", "--ci"], { cwd: APP });
162+ if (windowsFromLinux) {
163+ // Tauri's cross build: cargo-xwin for the MSVC target, NSIS for the
164+ // installer, both on PATH (the release workflow installs them).
165+ console.error("building windows-x64 from here");
166+ run("rustup", ["target", "add", PLATFORMS["windows-x64"].triple]);
167+ run("npx", ["tauri", "build", "--ci", "--runner", "cargo-xwin", "--target", PLATFORMS["windows-x64"].triple, "--bundles", "nsis"], { cwd: APP });
168+ }
169+}
170+
171+/**
172+ * Copies this machine's bundles into the release folder under their
173+ * release names, with each updater file's signature, and notes what each
174+ * is in collect.json there, one line per file, so folders from several
175+ * machines merge by copying them together.
176+ */
177+function collect() {
178+ const v = version();
179+ const dir = outDir(v);
180+ mkdirSync(dir, { recursive: true });
181+ const entries = [];
182+ for (const { platform, dir: bundles } of bundleDirs()) {
183+ for (const file of bundleFiles(bundles)) {
184+ const entry = classify(basename(file), platform, v);
185+ if (!entry) continue;
186+ const bytes = readFileSync(file);
187+ copyFileSync(file, join(dir, entry.name));
188+ let signature = null;
189+ if (entry.updater) {
190+ const sig = `${file}.sig`;
191+ if (!existsSync(sig)) throw new Error(`${basename(file)} has no .sig: was TAURI_SIGNING_PRIVATE_KEY set when it was built?`);
192+ signature = readFileSync(sig, "utf8").trim();
193+ copyFileSync(sig, join(dir, `${entry.name}.sig`));
194+ }
195+ entries.push({ ...entry, size: bytes.length, sha256: sha256(bytes), signature });
196+ console.error(`collected ${entry.name}`);
197+ }
198+ }
199+ if (entries.length === 0) throw new Error("no bundles found: run build first");
200+ // One notes file per machine, so the folders of several machines merge
201+ // by copying them together (the release workflow does).
202+ writeFileSync(join(dir, `collect-${hostPlatform()}.json`), `${JSON.stringify(entries, null, 2)}\n`);
203+ console.log(`collected ${entries.length} files of ${v} into ${dir}`);
204+}
205+
206+const isNotes = (name) => /^collect-.*\.json$/.test(name);
207+
208+function writeManifest(notes) {
209+ const v = version();
210+ const dir = outDir(v);
211+ const collected = existsSync(dir) ? readdirSync(dir).filter(isNotes) : [];
212+ if (collected.length === 0) throw new Error(`${dir} has no collect-*.json: run collect first`);
213+ const entries = collected.flatMap((name) => JSON.parse(readFileSync(join(dir, name), "utf8")));
214+ const m = manifest(entries, v, { notes });
215+ writeFileSync(join(dir, "manifest.json"), `${JSON.stringify(m, null, 2)}\n`);
216+ writeFileSync(join(dir, "latest.json"), `${JSON.stringify(m, null, 2)}\n`);
217+ writeFileSync(join(dir, "SHA256SUMS"), entries.map((e) => `${e.sha256} ${e.name}`).join("\n") + "\n");
218+ console.log(`manifest for ${v}: ${Object.keys(m.platforms).length} platforms the updater serves, ${m.downloads.length} downloads`);
219+}
220+
221+function publish(dryRun) {
222+ const v = version();
223+ const dir = outDir(v);
224+ if (!existsSync(join(dir, "latest.json"))) throw new Error("no latest.json: run manifest first");
225+ const put = (key, file, type) => {
226+ const args = ["wrangler", "r2", "object", "put", `${BUCKET}/${key}`, "--file", file, "--remote", "--content-type", type];
227+ if (dryRun) console.log(`would put ${key}`);
228+ else run("npx", args, { cwd: join(ROOT, "apps/web") });
229+ };
230+ const type = (name) => (name.endsWith(".json") ? "application/json" : name.endsWith(".sig") || name === "SHA256SUMS" ? "text/plain" : "application/octet-stream");
231+ // The version's files first; latest.json last, so no app is pointed at
232+ // files that are not there yet.
233+ for (const name of readdirSync(dir)) {
234+ if (name === "latest.json" || isNotes(name)) continue;
235+ put(`desktop/${v}/${name}`, join(dir, name), type(name));
236+ }
237+ put("desktop/latest.json", join(dir, "latest.json"), "application/json");
238+}
239+
240+if (process.argv[1]?.replaceAll("\\", "/").endsWith("scripts/desktop-release.mjs")) {
241+ const [command, ...rest] = process.argv.slice(2);
242+ const option = (name) => {
243+ const at = rest.indexOf(`--${name}`);
244+ return at >= 0 ? rest[at + 1] : undefined;
245+ };
246+ try {
247+ switch (command) {
248+ case "keygen":
249+ keygen();
250+ break;
251+ case "build":
252+ build(rest.includes("--windows-from-linux"));
253+ break;
254+ case "collect":
255+ collect();
256+ break;
257+ case "manifest":
258+ writeManifest(option("notes") ?? "");
259+ break;
260+ case "publish":
261+ publish(rest.includes("--dry-run"));
262+ break;
263+ default:
264+ console.error("usage: node scripts/desktop-release.mjs keygen|build|collect|manifest|publish");
265+ process.exit(2);
266+ }
267+ } catch (error) {
268+ console.error(String(error.message ?? error));
269+ process.exit(1);
270+ }
271+}
+59−0
1+import assert from "node:assert/strict";
2+import { test } from "node:test";
3+
4+import { classify, hostPlatform, manifest, platformOfTriple } from "./desktop-release.mjs";
5+
6+test("a bundle's place in a release", () => {
7+ assert.deepEqual(classify("g1t_0.1.0_x64-setup.exe", "windows-x64", "0.1.0"), {
8+ platform: "windows",
9+ arch: "x64",
10+ kind: "installer",
11+ name: "g1t-0.1.0-windows-x64-setup.exe",
12+ updater: true,
13+ });
14+ assert.deepEqual(classify("g1t.app.tar.gz", "macos-arm64", "0.1.0"), {
15+ platform: "macos",
16+ arch: "arm64",
17+ kind: "update",
18+ name: "g1t-0.1.0-macos-arm64.app.tar.gz",
19+ updater: true,
20+ });
21+ assert.equal(classify("g1t_0.1.0_aarch64.dmg", "macos-arm64", "0.1.0").kind, "dmg");
22+ assert.equal(classify("g1t_0.1.0_amd64.AppImage", "linux-x64", "0.1.0").name, "g1t-0.1.0-linux-x64.AppImage");
23+ assert.equal(classify("g1t_0.1.0_amd64.deb", "linux-x64", "0.1.0").updater, false);
24+ assert.equal(classify("g1t-0.1.0-1.x86_64.rpm", "linux-x64", "0.1.0").kind, "rpm");
25+ // Signatures ride with their file; an MSI is not published.
26+ assert.equal(classify("g1t_0.1.0_x64-setup.exe.sig", "windows-x64", "0.1.0"), null);
27+ assert.equal(classify("g1t_0.1.0_x64_en-US.msi", "windows-x64", "0.1.0"), null);
28+});
29+
30+test("platforms, from a machine and from a target", () => {
31+ assert.equal(hostPlatform("win32", "x64"), "windows-x64");
32+ assert.equal(hostPlatform("darwin", "arm64"), "macos-arm64");
33+ assert.equal(hostPlatform("linux", "x64"), "linux-x64");
34+ assert.equal(platformOfTriple("x86_64-pc-windows-msvc"), "windows-x64");
35+ assert.equal(platformOfTriple("aarch64-apple-darwin"), "macos-arm64");
36+ assert.equal(platformOfTriple("debug"), null);
37+});
38+
39+test("the manifest serves the updater and the download page", () => {
40+ const entries = [
41+ { platform: "windows", arch: "x64", kind: "installer", name: "g1t-0.1.0-windows-x64-setup.exe", updater: true, size: 10, sha256: "a", signature: "sig-win" },
42+ { platform: "macos", arch: "arm64", kind: "update", name: "g1t-0.1.0-macos-arm64.app.tar.gz", updater: true, size: 20, sha256: "b", signature: "sig-mac" },
43+ { platform: "macos", arch: "arm64", kind: "dmg", name: "g1t-0.1.0-macos-arm64.dmg", updater: false, size: 30, sha256: "c", signature: null },
44+ { platform: "linux", arch: "x64", kind: "deb", name: "g1t-0.1.0-linux-x64.deb", updater: false, size: 40, sha256: "d", signature: null },
45+ ];
46+ const m = manifest(entries, "0.1.0", { date: "2026-10-11T00:00:00.000Z", notes: "First." });
47+ assert.equal(m.version, "0.1.0");
48+ assert.equal(m.notes, "First.");
49+ assert.deepEqual(m.platforms, {
50+ "windows-x86_64": { url: "https://g1t.sh/downloads/desktop/0.1.0/g1t-0.1.0-windows-x64-setup.exe", signature: "sig-win" },
51+ "darwin-aarch64": { url: "https://g1t.sh/downloads/desktop/0.1.0/g1t-0.1.0-macos-arm64.app.tar.gz", signature: "sig-mac" },
52+ });
53+ // The update archive is the updater's alone; people download the disk image.
54+ assert.deepEqual(
55+ m.downloads.map((d) => d.name),
56+ ["g1t-0.1.0-linux-x64.deb", "g1t-0.1.0-macos-arm64.dmg", "g1t-0.1.0-windows-x64-setup.exe"],
57+ );
58+ assert.deepEqual(m.downloads[1], { platform: "macos", arch: "arm64", kind: "dmg", name: "g1t-0.1.0-macos-arm64.dmg", size: 30, sha256: "c" });
59+});