diff --git a/.gitignore b/.gitignore
index 67c3c05..bba85c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,5 +23,8 @@
# Deploy / local
*.tar.gz
+dist-desktop/
+desktop/updater.key
+**/.tauri/
data/mobile-releases/*.apk
scripts/deploy-target.local.env
diff --git a/deploy_server_guide.md b/deploy_server_guide.md
index a505f92..2e76090 100644
--- a/deploy_server_guide.md
+++ b/deploy_server_guide.md
@@ -672,6 +672,24 @@ ssh aidev@exdev.co.kr "docker info"
---
+## PC 데스크톱 클라이언트 (Tauri)
+
+웹 Docker 배포와 **별도 Jenkins Job** `goldenChart-Desktop-Pipeline` 으로 macOS `.dmg` + Windows NSIS `.exe` 를 빌드합니다.
+
+```bash
+# 서버 최초 1회
+./scripts/install-desktop-build-deps.sh
+./scripts/setup-desktop-updater-keys.sh
+./scripts/server-install-desktop-jenkins.sh
+
+# 수동 빌드·배포
+./scripts/jenkins-desktop-pipeline.sh
+```
+
+상세: [docs/desktop-pilot.md](docs/desktop-pilot.md)
+
+---
+
## 13. 부록: deploy.sh · post-receive · Jenkinsfile
### 13-1. deploy.sh (서버용)
diff --git a/desktop/.env.example b/desktop/.env.example
new file mode 100644
index 0000000..f56e006
--- /dev/null
+++ b/desktop/.env.example
@@ -0,0 +1 @@
+VITE_API_BASE_URL=https://exdev.co.kr/api
diff --git a/desktop/.gitignore b/desktop/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/desktop/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/desktop/README.md b/desktop/README.md
new file mode 100644
index 0000000..7fb8713
--- /dev/null
+++ b/desktop/README.md
@@ -0,0 +1,65 @@
+# GoldenChart Desktop (Tauri)
+
+PC용 클라이언트 — [`frontend/src`](../frontend/src)를 `@frontend` alias로 재사용합니다.
+API·DB는 exdev 서버에 연결하며, 웹 브라우저와 병행 사용 가능합니다.
+
+## 개발
+
+```bash
+# 루트에서 — 최초 1회 (Homebrew + Rust + NSIS/LLVM)
+npm run install:desktop:deps # 전체 (Windows 크로스 포함)
+npm run install:desktop:deps -- --mac-only # macOS 빌드만
+
+# PATH (새 터미널 또는)
+source ~/.zprofile
+# 또는
+source scripts/desktop-dev-path.sh
+```
+
+사전 요구: [Tauri prerequisites](https://tauri.app/start/prerequisites/) (Rust, Xcode CLI 등)
+
+## 빌드
+
+```bash
+npm run build:desktop:mac # macOS .dmg
+npm run build:desktop:win # Windows NSIS (macOS에서 cargo-xwin 크로스 빌드)
+./scripts/build-desktop.sh --all
+```
+
+산출물: `desktop/src-tauri/target/release/bundle/dmg/GoldenChart_*.dmg`
+
+### macOS dmg 설치
+
+1. dmg 더블클릭 → **GoldenChart.app** 을 **Applications(응용 프로그램)** 로 **드래그** (자동 설치 아님)
+2. **Finder → 응용 프로그램 → GoldenChart** 실행 (`goldenChart` 아님, **GoldenChart**)
+3. Launchpad에 없으면 Spotlight(`⌘+Space`)에 `GoldenChart` 검색
+4. 창을 닫아도 **메뉴 막대 트레이**에 남음 — 트레이 아이콘 클릭으로 다시 열기
+5. 최초 실행: **시스템 설정 → 개인정보 보호 → 확인 없이 열기**
+
+## 주요 기능
+
+- **위젯 OS 창**: `WebviewWindow` + `widget.html`
+- **트레이 상주**: 닫기 → 숨김, STOMP 백그라운드 유지
+- **OS 알림**: 매매 시그널 → `tauri-plugin-notification`
+- **업데이트**: 설정 → PC 앱 → 업데이트 확인 (`tauri-plugin-updater`)
+
+자세한 배포·파일럿: [docs/desktop-pilot.md](../docs/desktop-pilot.md)
+
+## 운영 스크립트
+
+| 스크립트 | 용도 |
+|---------|------|
+| `scripts/install-desktop-build-deps.sh` | Rust, NSIS, cargo-xwin (macOS Jenkins) |
+| `scripts/setup-desktop-updater-keys.sh` | updater minisign 키 생성 |
+| `scripts/build-desktop.sh` | dmg + Windows NSIS + updater bundles |
+| `scripts/publish-desktop-update.sh` | 서명 + latest.json + static 배포 |
+| `scripts/jenkins-desktop-pipeline.sh` | Jenkins 전체 파이프라인 |
+| `scripts/server-install-desktop-jenkins.sh` | Jenkins Job XML 생성 |
+| `scripts/smoke-test-desktop.sh` | CI smoke test |
+
+```bash
+npm run install:desktop:deps
+npm run setup:desktop:keys
+npm run publish:desktop
+npm run smoke:desktop
+```
diff --git a/desktop/index.html b/desktop/index.html
new file mode 100644
index 0000000..247393b
--- /dev/null
+++ b/desktop/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ GoldenChart
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/package.json b/desktop/package.json
new file mode 100644
index 0000000..cb08e32
--- /dev/null
+++ b/desktop/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@goldenchart/desktop",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "dev:desktop": "tauri dev",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "tauri": "tauri",
+ "tauri:dev": "tauri dev",
+ "tauri:build": "tauri build",
+ "tauri:build:mac": "bash -c 'source ../scripts/prepare-tauri-build.sh && exec tauri build'",
+ "tauri:build:win": "bash -c 'source ../scripts/prepare-tauri-build.sh && exec tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis'"
+ },
+ "dependencies": {
+ "@goldenchart/shared": "*",
+ "@stomp/stompjs": "^7.3.0",
+ "@tanstack/react-virtual": "^3.14.2",
+ "@tauri-apps/api": "^2",
+ "@tauri-apps/plugin-notification": "^2",
+ "@tauri-apps/plugin-opener": "^2",
+ "@tauri-apps/plugin-process": "^2",
+ "@tauri-apps/plugin-updater": "^2",
+ "@xyflow/react": "^12.10.2",
+ "lightweight-charts": "^5.2.0",
+ "lightweight-charts-indicators": "^0.4.1",
+ "oakscriptjs": "^0.2.8",
+ "qrcode": "^1.5.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "sockjs-client": "^1.6.1"
+ },
+ "devDependencies": {
+ "@tauri-apps/cli": "^2",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^18.3.1",
+ "@types/react-dom": "^18.3.1",
+ "@types/sockjs-client": "^1.5.4",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.0"
+ }
+}
diff --git a/desktop/public/tauri.svg b/desktop/public/tauri.svg
new file mode 100644
index 0000000..31b62c9
--- /dev/null
+++ b/desktop/public/tauri.svg
@@ -0,0 +1,6 @@
+
diff --git a/desktop/public/vite.svg b/desktop/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/desktop/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/desktop/src-tauri/.gitignore b/desktop/src-tauri/.gitignore
new file mode 100644
index 0000000..b21bd68
--- /dev/null
+++ b/desktop/src-tauri/.gitignore
@@ -0,0 +1,7 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# Generated by Tauri
+# will have schema files for capabilities auto-completion
+/gen/schemas
diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock
new file mode 100644
index 0000000..3d0910a
--- /dev/null
+++ b/desktop/src-tauri/Cargo.lock
@@ -0,0 +1,5658 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "async-broadcast"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "atk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
+dependencies = [
+ "atk-sys",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2",
+]
+
+[[package]]
+name = "blocking"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "brotli"
+version = "8.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cairo-rs"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
+dependencies = [
+ "bitflags 2.13.0",
+ "cairo-sys-rs",
+ "glib",
+ "libc",
+ "once_cell",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "camino"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
+dependencies = [
+ "serde",
+ "toml 0.9.12+spec-1.1.0",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
+dependencies = [
+ "bitflags 2.13.0",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
+dependencies = [
+ "bitflags 2.13.0",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "phf",
+ "smallvec",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
+dependencies = [
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "ctor"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
+dependencies = [
+ "ctor-proc-macro",
+ "dtor",
+]
+
+[[package]]
+name = "ctor-proc-macro"
+version = "0.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "dbus"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "libc",
+ "objc2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "dlopen2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
+dependencies = [
+ "dlopen2_derive",
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "dlopen2_derive"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "dom_query"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
+dependencies = [
+ "bit-set",
+ "cssparser",
+ "foldhash 0.2.0",
+ "html5ever",
+ "precomputed-hash",
+ "selectors",
+ "tendril",
+]
+
+[[package]]
+name = "dpi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "dtoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dtor"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4"
+dependencies = [
+ "dtor-proc-macro",
+]
+
+[[package]]
+name = "dtor-proc-macro"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "embed-resource"
+version = "3.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb"
+dependencies = [
+ "cc",
+ "memchr",
+ "rustc_version",
+ "toml 1.1.2+spec-1.1.0",
+ "vswhom",
+ "winreg",
+]
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "endi"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
+dependencies = [
+ "memoffset",
+ "rustc_version",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
+dependencies = [
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "gdk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
+dependencies = [
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkwayland-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkx11"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
+dependencies = [
+ "gdk",
+ "gdkx11-sys",
+ "gio",
+ "glib",
+ "libc",
+ "x11",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "libc",
+ "system-deps",
+ "x11",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "wasip2",
+ "wasip3",
+]
+
+[[package]]
+name = "gio"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys",
+ "glib",
+ "libc",
+ "once_cell",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "glib"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
+dependencies = [
+ "bitflags 2.13.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "memchr",
+ "once_cell",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-crate 2.0.2",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "gobject-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "goldenchart-desktop"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-notification",
+ "tauri-plugin-opener",
+ "tauri-plugin-process",
+ "tauri-plugin-updater",
+]
+
+[[package]]
+name = "gtk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
+dependencies = [
+ "atk",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "html5ever"
+version = "0.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
+dependencies = [
+ "log",
+ "markup5ever",
+]
+
+[[package]]
+name = "http"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ico"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
+dependencies = [
+ "byteorder",
+ "png 0.17.16",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "infer"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "is-docker"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "is-wsl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
+dependencies = [
+ "is-docker",
+ "once_cell",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "javascriptcore-rs"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "glib",
+ "javascriptcore-rs-sys",
+]
+
+[[package]]
+name = "javascriptcore-rs-sys"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys 0.4.1",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.18",
+ "walkdir",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "jni-macros"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
+dependencies = [
+ "jsonptr",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "jsonptr"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
+dependencies = [
+ "bitflags 2.13.0",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
+[[package]]
+name = "libappindicator"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a"
+dependencies = [
+ "glib",
+ "gtk",
+ "gtk-sys",
+ "libappindicator-sys",
+ "log",
+]
+
+[[package]]
+name = "libappindicator-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
+dependencies = [
+ "gtk-sys",
+ "libloading",
+ "once_cell",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "libdbus-sys"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libredox"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+
+[[package]]
+name = "mac-notification-sys"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04fd0110fd05744c3c904acf1a8ca624a721d75a35638f127cd5fb6b7ccfb1bf"
+dependencies = [
+ "cc",
+ "log",
+ "objc2",
+ "objc2-foundation",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "markup5ever"
+version = "0.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862"
+dependencies = [
+ "log",
+ "tendril",
+ "web_atoms",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minisign-verify"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "muda"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c"
+dependencies = [
+ "crossbeam-channel",
+ "dpi",
+ "gtk",
+ "keyboard-types",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "once_cell",
+ "png 0.18.1",
+ "serde",
+ "thiserror 2.0.18",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "ndk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
+dependencies = [
+ "bitflags 2.13.0",
+ "jni-sys 0.3.1",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "ndk-sys"
+version = "0.6.0+11769913"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
+dependencies = [
+ "jni-sys 0.3.1",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
+[[package]]
+name = "notify-rust"
+version = "4.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50ff2e74231b72c832d82982193b417f230945be6bdb5575b251d941d31adb00"
+dependencies = [
+ "futures-lite",
+ "log",
+ "mac-notification-sys",
+ "serde",
+ "tauri-winrt-notification",
+ "zbus",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
+dependencies = [
+ "proc-macro-crate 3.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
+dependencies = [
+ "objc2-encode",
+ "objc2-exception-helper",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.13.0",
+ "dispatch2",
+ "objc2",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.13.0",
+ "dispatch2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-text"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-exception-helper"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "libc",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-osa-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.13.0",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "objc2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-core-text",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-web-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "open"
+version = "5.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c"
+dependencies = [
+ "dunce",
+ "is-wsl",
+ "libc",
+ "pathdiff",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "osakit"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+ "objc2-osa-kit",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "pango"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
+dependencies = [
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+
+[[package]]
+name = "plist"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
+dependencies = [
+ "base64 0.22.1",
+ "indexmap 2.14.0",
+ "quick-xml 0.39.4",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "png"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
+dependencies = [
+ "bitflags 2.13.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
+dependencies = [
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit 0.25.12+spec-1.1.0",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.37.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.39.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.13.0",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.17",
+ "libredox",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "reqwest"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
+ "serde",
+ "serde_json",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags 2.13.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
+dependencies = [
+ "core-foundation",
+ "core-foundation-sys",
+ "jni 0.22.4",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "schemars"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
+dependencies = [
+ "dyn-clone",
+ "indexmap 1.9.3",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
+dependencies = [
+ "bitflags 2.13.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "selectors"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
+dependencies = [
+ "bitflags 2.13.0",
+ "cssparser",
+ "derive_more",
+ "log",
+ "new_debug_unreachable",
+ "phf",
+ "phf_codegen",
+ "precomputed-hash",
+ "rustc-hash",
+ "servo_arc",
+ "smallvec",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
+dependencies = [
+ "base64 0.22.1",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.0",
+ "schemars 0.9.0",
+ "schemars 1.2.1",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+
+[[package]]
+name = "simd_cesu8"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "socket2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "softbuffer"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
+dependencies = [
+ "bytemuck",
+ "js-sys",
+ "ndk",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "raw-window-handle",
+ "redox_syscall",
+ "tracing",
+ "wasm-bindgen",
+ "web-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "soup3"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
+dependencies = [
+ "futures-channel",
+ "gio",
+ "glib",
+ "libc",
+ "soup3-sys",
+]
+
+[[package]]
+name = "soup3-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "string_cache"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
+dependencies = [
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared",
+ "precomputed-hash",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "swift-rs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7"
+dependencies = [
+ "base64 0.21.7",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml 0.8.2",
+ "version-compare",
+]
+
+[[package]]
+name = "tao"
+version = "0.35.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
+dependencies = [
+ "bitflags 2.13.0",
+ "block2",
+ "core-foundation",
+ "core-graphics",
+ "crossbeam-channel",
+ "dbus",
+ "dispatch2",
+ "dlopen2",
+ "dpi",
+ "gdkwayland-sys",
+ "gdkx11-sys",
+ "gtk",
+ "jni 0.21.1",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-sys",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "once_cell",
+ "parking_lot",
+ "percent-encoding",
+ "raw-window-handle",
+ "tao-macros",
+ "unicode-segmentation",
+ "url",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "tao-macros"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
+[[package]]
+name = "tauri"
+version = "2.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "cookie",
+ "dirs",
+ "dunce",
+ "embed_plist",
+ "getrandom 0.3.4",
+ "glob",
+ "gtk",
+ "heck 0.5.0",
+ "http",
+ "jni 0.21.1",
+ "libc",
+ "log",
+ "mime",
+ "muda",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "percent-encoding",
+ "plist",
+ "raw-window-handle",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "swift-rs",
+ "tauri-build",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils",
+ "thiserror 2.0.18",
+ "tokio",
+ "tray-icon",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "window-vibrancy",
+ "windows",
+]
+
+[[package]]
+name = "tauri-build"
+version = "2.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "dirs",
+ "glob",
+ "heck 0.5.0",
+ "json-patch",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "tauri-winres",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "2.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9"
+dependencies = [
+ "base64 0.22.1",
+ "brotli",
+ "ico",
+ "json-patch",
+ "plist",
+ "png 0.17.16",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "serde",
+ "serde_json",
+ "sha2",
+ "syn 2.0.117",
+ "tauri-utils",
+ "thiserror 2.0.18",
+ "time",
+ "url",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "2.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "tauri-codegen",
+ "tauri-utils",
+]
+
+[[package]]
+name = "tauri-plugin"
+version = "2.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e"
+dependencies = [
+ "anyhow",
+ "glob",
+ "plist",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-plugin-notification"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc"
+dependencies = [
+ "log",
+ "notify-rust",
+ "rand",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.18",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-opener"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29"
+dependencies = [
+ "dunce",
+ "glob",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "open",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.18",
+ "url",
+ "windows",
+ "zbus",
+]
+
+[[package]]
+name = "tauri-plugin-process"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
+dependencies = [
+ "tauri",
+ "tauri-plugin",
+]
+
+[[package]]
+name = "tauri-plugin-updater"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
+dependencies = [
+ "base64 0.22.1",
+ "dirs",
+ "flate2",
+ "futures-util",
+ "http",
+ "infer",
+ "log",
+ "minisign-verify",
+ "osakit",
+ "percent-encoding",
+ "reqwest",
+ "rustls",
+ "semver",
+ "serde",
+ "serde_json",
+ "tar",
+ "tauri",
+ "tauri-plugin",
+ "tempfile",
+ "thiserror 2.0.18",
+ "time",
+ "tokio",
+ "url",
+ "windows-sys 0.60.2",
+ "zip",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "2.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef"
+dependencies = [
+ "cookie",
+ "dpi",
+ "gtk",
+ "http",
+ "jni 0.21.1",
+ "objc2",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "thiserror 2.0.18",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "2.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
+dependencies = [
+ "gtk",
+ "http",
+ "jni 0.21.1",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "softbuffer",
+ "tao",
+ "tauri-runtime",
+ "tauri-utils",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95"
+dependencies = [
+ "anyhow",
+ "brotli",
+ "cargo_metadata",
+ "ctor",
+ "dom_query",
+ "dunce",
+ "glob",
+ "http",
+ "infer",
+ "json-patch",
+ "log",
+ "memchr",
+ "phf",
+ "plist",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde-untagged",
+ "serde_json",
+ "serde_with",
+ "swift-rs",
+ "thiserror 2.0.18",
+ "toml 1.1.2+spec-1.1.0",
+ "url",
+ "urlpattern",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-winres"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6"
+dependencies = [
+ "dunce",
+ "embed-resource",
+ "toml 1.1.2+spec-1.1.0",
+]
+
+[[package]]
+name = "tauri-winrt-notification"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
+dependencies = [
+ "quick-xml 0.37.5",
+ "thiserror 2.0.18",
+ "windows",
+ "windows-version",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.2",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tendril"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24"
+dependencies = [
+ "new_debug_unreachable",
+ "utf-8",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "time"
+version = "0.3.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
+dependencies = [
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "toml"
+version = "0.9.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
+dependencies = [
+ "indexmap 2.14.0",
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
+dependencies = [
+ "indexmap 2.14.0",
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 1.0.3",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.14.0",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
+dependencies = [
+ "indexmap 2.14.0",
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
+dependencies = [
+ "indexmap 2.14.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "winnow 1.0.3",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
+dependencies = [
+ "winnow 1.0.3",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.13.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tray-icon"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773"
+dependencies = [
+ "crossbeam-channel",
+ "dirs",
+ "libappindicator",
+ "muda",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "once_cell",
+ "png 0.18.1",
+ "serde",
+ "thiserror 2.0.18",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "uds_windows"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "urlpattern"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d"
+dependencies = [
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
+dependencies = [
+ "getrandom 0.4.2",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version-compare"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vswhom"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
+dependencies = [
+ "libc",
+ "vswhom-sys",
+]
+
+[[package]]
+name = "vswhom-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen 0.57.1",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen 0.51.0",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap 2.14.0",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.13.0",
+ "hashbrown 0.15.5",
+ "indexmap 2.14.0",
+ "semver",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web_atoms"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538"
+dependencies = [
+ "phf",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+]
+
+[[package]]
+name = "webkit2gtk"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-rs",
+ "gdk",
+ "gdk-sys",
+ "gio",
+ "gio-sys",
+ "glib",
+ "glib-sys",
+ "gobject-sys",
+ "gtk",
+ "gtk-sys",
+ "javascriptcore-rs",
+ "libc",
+ "once_cell",
+ "soup3",
+ "webkit2gtk-sys",
+]
+
+[[package]]
+name = "webkit2gtk-sys"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-sys-rs",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pkg-config",
+ "soup3-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-implement",
+ "windows-interface",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
+dependencies = [
+ "thiserror 2.0.18",
+ "windows",
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window-vibrancy"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
+dependencies = [
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "raw-window-handle",
+ "windows-sys 0.59.0",
+ "windows-version",
+]
+
+[[package]]
+name = "windows"
+version = "0.61.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+dependencies = [
+ "windows-collections",
+ "windows-core 0.61.2",
+ "windows-future",
+ "windows-link 0.1.3",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.2.1",
+ "windows-result 0.4.1",
+ "windows-strings 0.5.1",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link 0.2.1",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-version"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+
+[[package]]
+name = "winnow"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winreg"
+version = "0.55.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "indexmap 2.14.0",
+ "prettyplease",
+ "syn 2.0.117",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.13.0",
+ "indexmap 2.14.0",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.14.0",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "wry"
+version = "0.55.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
+dependencies = [
+ "base64 0.22.1",
+ "block2",
+ "cookie",
+ "crossbeam-channel",
+ "dirs",
+ "dom_query",
+ "dpi",
+ "dunce",
+ "gdkx11",
+ "gtk",
+ "http",
+ "javascriptcore-rs",
+ "jni 0.21.1",
+ "libc",
+ "ndk",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "sha2",
+ "soup3",
+ "tao-macros",
+ "thiserror 2.0.18",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "x11"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "synstructure",
+]
+
+[[package]]
+name = "zbus"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "libc",
+ "ordered-stream",
+ "rustix",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.2",
+ "winnow 1.0.3",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
+dependencies = [
+ "proc-macro-crate 3.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
+dependencies = [
+ "serde",
+ "winnow 1.0.3",
+ "zvariant",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "zip"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "indexmap 2.14.0",
+ "memchr",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+
+[[package]]
+name = "zvariant"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "winnow 1.0.3",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
+dependencies = [
+ "proc-macro-crate 3.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 2.0.117",
+ "winnow 1.0.3",
+]
diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml
new file mode 100644
index 0000000..18fa616
--- /dev/null
+++ b/desktop/src-tauri/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "goldenchart-desktop"
+version = "0.1.0"
+description = "GoldenChart Desktop Client"
+authors = ["GoldenChart"]
+edition = "2021"
+
+[lib]
+name = "goldenchart_desktop_lib"
+crate-type = ["staticlib", "cdylib", "rlib"]
+
+[build-dependencies]
+tauri-build = { version = "2", features = [] }
+
+[dependencies]
+tauri = { version = "2", features = ["tray-icon"] }
+tauri-plugin-notification = "2"
+tauri-plugin-updater = "2"
+tauri-plugin-process = "2"
+tauri-plugin-opener = "2"
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
diff --git a/desktop/src-tauri/Info.plist b/desktop/src-tauri/Info.plist
new file mode 100644
index 0000000..e9618bc
--- /dev/null
+++ b/desktop/src-tauri/Info.plist
@@ -0,0 +1,21 @@
+
+
+
+
+ LSApplicationCategoryType
+ public.app-category.finance
+ NSAppTransportSecurity
+
+ NSExceptionDomains
+
+ exdev.co.kr
+
+ NSExceptionAllowsInsecureHTTPLoads
+
+ NSIncludesSubdomains
+
+
+
+
+
+
diff --git a/desktop/src-tauri/build.rs b/desktop/src-tauri/build.rs
new file mode 100644
index 0000000..d860e1e
--- /dev/null
+++ b/desktop/src-tauri/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ tauri_build::build()
+}
diff --git a/desktop/src-tauri/capabilities/default.json b/desktop/src-tauri/capabilities/default.json
new file mode 100644
index 0000000..3794044
--- /dev/null
+++ b/desktop/src-tauri/capabilities/default.json
@@ -0,0 +1,27 @@
+{
+ "$schema": "../gen/schemas/desktop-schema.json",
+ "identifier": "default",
+ "description": "GoldenChart desktop main + widget windows",
+ "windows": ["main", "widget-*"],
+ "permissions": [
+ "core:default",
+ "core:window:allow-create",
+ "core:window:allow-close",
+ "core:window:allow-hide",
+ "core:window:allow-show",
+ "core:window:allow-set-focus",
+ "core:window:allow-start-dragging",
+ "core:webview:allow-create-webview-window",
+ "core:webview:allow-webview-close",
+ "notification:default",
+ "notification:allow-is-permission-granted",
+ "notification:allow-request-permission",
+ "notification:allow-notify",
+ "updater:default",
+ "updater:allow-check",
+ "updater:allow-download-and-install",
+ "process:allow-restart",
+ "process:allow-exit",
+ "opener:default"
+ ]
+}
diff --git a/desktop/src-tauri/icons/128x128.png b/desktop/src-tauri/icons/128x128.png
new file mode 100644
index 0000000..6be5e50
Binary files /dev/null and b/desktop/src-tauri/icons/128x128.png differ
diff --git a/desktop/src-tauri/icons/128x128@2x.png b/desktop/src-tauri/icons/128x128@2x.png
new file mode 100644
index 0000000..e81bece
Binary files /dev/null and b/desktop/src-tauri/icons/128x128@2x.png differ
diff --git a/desktop/src-tauri/icons/32x32.png b/desktop/src-tauri/icons/32x32.png
new file mode 100644
index 0000000..a437dd5
Binary files /dev/null and b/desktop/src-tauri/icons/32x32.png differ
diff --git a/desktop/src-tauri/icons/Square107x107Logo.png b/desktop/src-tauri/icons/Square107x107Logo.png
new file mode 100644
index 0000000..0ca4f27
Binary files /dev/null and b/desktop/src-tauri/icons/Square107x107Logo.png differ
diff --git a/desktop/src-tauri/icons/Square142x142Logo.png b/desktop/src-tauri/icons/Square142x142Logo.png
new file mode 100644
index 0000000..b81f820
Binary files /dev/null and b/desktop/src-tauri/icons/Square142x142Logo.png differ
diff --git a/desktop/src-tauri/icons/Square150x150Logo.png b/desktop/src-tauri/icons/Square150x150Logo.png
new file mode 100644
index 0000000..624c7bf
Binary files /dev/null and b/desktop/src-tauri/icons/Square150x150Logo.png differ
diff --git a/desktop/src-tauri/icons/Square284x284Logo.png b/desktop/src-tauri/icons/Square284x284Logo.png
new file mode 100644
index 0000000..c021d2b
Binary files /dev/null and b/desktop/src-tauri/icons/Square284x284Logo.png differ
diff --git a/desktop/src-tauri/icons/Square30x30Logo.png b/desktop/src-tauri/icons/Square30x30Logo.png
new file mode 100644
index 0000000..6219700
Binary files /dev/null and b/desktop/src-tauri/icons/Square30x30Logo.png differ
diff --git a/desktop/src-tauri/icons/Square310x310Logo.png b/desktop/src-tauri/icons/Square310x310Logo.png
new file mode 100644
index 0000000..f9bc048
Binary files /dev/null and b/desktop/src-tauri/icons/Square310x310Logo.png differ
diff --git a/desktop/src-tauri/icons/Square44x44Logo.png b/desktop/src-tauri/icons/Square44x44Logo.png
new file mode 100644
index 0000000..d5fbfb2
Binary files /dev/null and b/desktop/src-tauri/icons/Square44x44Logo.png differ
diff --git a/desktop/src-tauri/icons/Square71x71Logo.png b/desktop/src-tauri/icons/Square71x71Logo.png
new file mode 100644
index 0000000..63440d7
Binary files /dev/null and b/desktop/src-tauri/icons/Square71x71Logo.png differ
diff --git a/desktop/src-tauri/icons/Square89x89Logo.png b/desktop/src-tauri/icons/Square89x89Logo.png
new file mode 100644
index 0000000..f3f705a
Binary files /dev/null and b/desktop/src-tauri/icons/Square89x89Logo.png differ
diff --git a/desktop/src-tauri/icons/StoreLogo.png b/desktop/src-tauri/icons/StoreLogo.png
new file mode 100644
index 0000000..4556388
Binary files /dev/null and b/desktop/src-tauri/icons/StoreLogo.png differ
diff --git a/desktop/src-tauri/icons/icon.icns b/desktop/src-tauri/icons/icon.icns
new file mode 100644
index 0000000..12a5bce
Binary files /dev/null and b/desktop/src-tauri/icons/icon.icns differ
diff --git a/desktop/src-tauri/icons/icon.ico b/desktop/src-tauri/icons/icon.ico
new file mode 100644
index 0000000..b3636e4
Binary files /dev/null and b/desktop/src-tauri/icons/icon.ico differ
diff --git a/desktop/src-tauri/icons/icon.png b/desktop/src-tauri/icons/icon.png
new file mode 100644
index 0000000..e1cd261
Binary files /dev/null and b/desktop/src-tauri/icons/icon.png differ
diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs
new file mode 100644
index 0000000..948bb3f
--- /dev/null
+++ b/desktop/src-tauri/src/lib.rs
@@ -0,0 +1,81 @@
+use tauri::{
+ menu::{MenuBuilder, MenuItemBuilder},
+ tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
+ AppHandle, Manager, RunEvent,
+};
+
+fn show_main_window(app: &AppHandle) {
+ if let Some(w) = app.get_webview_window("main") {
+ let _ = w.show();
+ let _ = w.unminimize();
+ let _ = w.set_focus();
+ }
+}
+
+#[cfg_attr(mobile, tauri::mobile_entry_point)]
+pub fn run() {
+ tauri::Builder::default()
+ .plugin(tauri_plugin_notification::init())
+ .plugin(tauri_plugin_updater::Builder::new().build())
+ .plugin(tauri_plugin_process::init())
+ .plugin(tauri_plugin_opener::init())
+ .setup(|app| {
+ let show_i = MenuItemBuilder::with_id("show", "GoldenChart 열기").build(app)?;
+ let quit_i = MenuItemBuilder::with_id("quit", "종료").build(app)?;
+ let menu = MenuBuilder::new(app)
+ .items(&[&show_i, &quit_i])
+ .build()?;
+
+ let icon = app
+ .default_window_icon()
+ .expect("missing tray icon")
+ .clone();
+
+ let _tray = TrayIconBuilder::new()
+ .icon(icon)
+ .menu(&menu)
+ .tooltip("GoldenChart")
+ .show_menu_on_left_click(false)
+ .on_menu_event(|app, event| match event.id.as_ref() {
+ "show" => show_main_window(app),
+ "quit" => app.exit(0),
+ _ => {}
+ })
+ .on_tray_icon_event(|tray, event| {
+ if let TrayIconEvent::Click {
+ button: MouseButton::Left,
+ button_state: MouseButtonState::Up,
+ ..
+ } = event
+ {
+ show_main_window(tray.app_handle());
+ }
+ })
+ .build(app)?;
+
+ show_main_window(app.handle());
+ Ok(())
+ })
+ .on_window_event(|window, event| {
+ if window.label() != "main" {
+ return;
+ }
+ if let tauri::WindowEvent::CloseRequested { api, .. } = event {
+ api.prevent_close();
+ let _ = window.hide();
+ }
+ })
+ .build(tauri::generate_context!())
+ .expect("error while building tauri application")
+ .run(|app_handle, event| match event {
+ #[cfg(target_os = "macos")]
+ RunEvent::Reopen { .. } => show_main_window(&app_handle),
+ RunEvent::ExitRequested { api, .. } => {
+ api.prevent_exit();
+ if let Some(w) = app_handle.get_webview_window("main") {
+ let _ = w.hide();
+ }
+ }
+ _ => {}
+ });
+}
diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs
new file mode 100644
index 0000000..294b734
--- /dev/null
+++ b/desktop/src-tauri/src/main.rs
@@ -0,0 +1,6 @@
+// Prevents additional console window on Windows in release, DO NOT REMOVE!!
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+fn main() {
+ goldenchart_desktop_lib::run()
+}
diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json
new file mode 100644
index 0000000..df1d1be
--- /dev/null
+++ b/desktop/src-tauri/tauri.conf.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://schema.tauri.app/config/2",
+ "productName": "GoldenChart",
+ "version": "0.1.0",
+ "identifier": "com.goldenchart.desktop",
+ "build": {
+ "beforeDevCommand": "npm run dev",
+ "devUrl": "http://localhost:5175",
+ "beforeBuildCommand": "npm run build",
+ "frontendDist": "../dist"
+ },
+ "app": {
+ "withGlobalTauri": true,
+ "windows": [
+ {
+ "label": "main",
+ "title": "GoldenChart",
+ "width": 1440,
+ "height": 900,
+ "minWidth": 960,
+ "minHeight": 640,
+ "center": true,
+ "visible": true
+ }
+ ],
+ "security": {
+ "csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src 'self' http://localhost:8080 http://127.0.0.1:8080 http://exdev.co.kr https://exdev.co.kr ws://exdev.co.kr wss://exdev.co.kr ws://localhost:8080 https://api.upbit.com wss://api.upbit.com https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;"
+ },
+ "trayIcon": {
+ "iconPath": "icons/icon.png",
+ "iconAsTemplate": true
+ }
+ },
+ "bundle": {
+ "active": true,
+ "targets": "all",
+ "createUpdaterArtifacts": false,
+ "icon": [
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
+ "icons/icon.ico"
+ ],
+ "macOS": {
+ "exceptionDomain": "exdev.co.kr",
+ "infoPlist": "Info.plist"
+ }
+ },
+ "plugins": {
+ "updater": {
+ "endpoints": [
+ "https://exdev.co.kr/desktop/updates/latest.json"
+ ],
+ "pubkey": "REPLACE_WITH_TAURI_SIGNER_PUBKEY"
+ }
+ }
+}
diff --git a/desktop/src/assets/react.svg b/desktop/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/desktop/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/desktop/src/bridge/installDesktopBridge.ts b/desktop/src/bridge/installDesktopBridge.ts
new file mode 100644
index 0000000..d3efc6b
--- /dev/null
+++ b/desktop/src/bridge/installDesktopBridge.ts
@@ -0,0 +1,17 @@
+import { openWidgetWindow, closeWidgetWindow, focusWidgetWindow } from './widgetWindows';
+import { showNativeNotification } from './notifications';
+import { checkForUpdates, getAppVersion } from './updater';
+import type { DesktopBridge } from './types';
+
+/** frontend App 로드 전 호출 — window.__goldenDesktopBridge 등록 */
+export function installDesktopBridge(): void {
+ const bridge: DesktopBridge = {
+ openWidgetWindow,
+ closeWidgetWindow,
+ focusWidgetWindow,
+ showNativeNotification,
+ checkForUpdates,
+ getAppVersion,
+ };
+ window.__goldenDesktopBridge = bridge;
+}
diff --git a/desktop/src/bridge/notifications.ts b/desktop/src/bridge/notifications.ts
new file mode 100644
index 0000000..75badff
--- /dev/null
+++ b/desktop/src/bridge/notifications.ts
@@ -0,0 +1,19 @@
+import {
+ isPermissionGranted,
+ requestPermission,
+ sendNotification,
+} from '@tauri-apps/plugin-notification';
+
+export async function showNativeNotification(title: string, body: string): Promise {
+ let granted = await isPermissionGranted();
+ if (!granted) {
+ const perm = await requestPermission();
+ granted = perm === 'granted';
+ }
+ if (!granted) return;
+
+ await sendNotification({
+ title,
+ body,
+ });
+}
diff --git a/desktop/src/bridge/types.ts b/desktop/src/bridge/types.ts
new file mode 100644
index 0000000..de38ccd
--- /dev/null
+++ b/desktop/src/bridge/types.ts
@@ -0,0 +1,21 @@
+/**
+ * 데스크톱(Tauri) 브릿지 타입 — frontend에서 window.__goldenDesktopBridge 로 호출
+ */
+import type { FloatingWidgetInstance } from '@frontend/types/floatingWidget';
+
+export interface DesktopBridge {
+ openWidgetWindow: (instance: FloatingWidgetInstance) => Promise;
+ closeWidgetWindow: (id: string) => Promise;
+ focusWidgetWindow: (id: string) => Promise;
+ showNativeNotification: (title: string, body: string) => Promise;
+ checkForUpdates: () => Promise<{ available: boolean; version?: string; message?: string }>;
+ getAppVersion: () => Promise;
+}
+
+declare global {
+ interface Window {
+ __goldenDesktopBridge?: DesktopBridge;
+ }
+}
+
+export {};
diff --git a/desktop/src/bridge/updater.ts b/desktop/src/bridge/updater.ts
new file mode 100644
index 0000000..796b878
--- /dev/null
+++ b/desktop/src/bridge/updater.ts
@@ -0,0 +1,26 @@
+import { check } from '@tauri-apps/plugin-updater';
+import { relaunch } from '@tauri-apps/plugin-process';
+import { getVersion } from '@tauri-apps/api/app';
+
+export async function getAppVersion(): Promise {
+ return getVersion();
+}
+
+export async function checkForUpdates(): Promise<{
+ available: boolean;
+ version?: string;
+ message?: string;
+}> {
+ try {
+ const update = await check();
+ if (!update) {
+ return { available: false, message: '최신 버전입니다.' };
+ }
+ await update.downloadAndInstall();
+ await relaunch();
+ return { available: true, version: update.version, message: '업데이트 설치 후 재시작합니다.' };
+ } catch (e) {
+ const msg = e instanceof Error ? e.message : '업데이트 확인 실패';
+ return { available: false, message: msg };
+ }
+}
diff --git a/desktop/src/bridge/widgetWindows.ts b/desktop/src/bridge/widgetWindows.ts
new file mode 100644
index 0000000..7ae04a0
--- /dev/null
+++ b/desktop/src/bridge/widgetWindows.ts
@@ -0,0 +1,63 @@
+import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
+import type { FloatingWidgetInstance } from '@frontend/types/floatingWidget';
+
+const widgetWindows = new Map();
+
+function widgetUrl(instance: FloatingWidgetInstance): string {
+ const params = new URLSearchParams({
+ id: instance.id,
+ rows: String(instance.rows),
+ cols: String(instance.cols),
+ width: String(instance.width),
+ height: String(instance.height),
+ });
+ if (instance.rowFr?.length) params.set('rowFr', instance.rowFr.join(','));
+ if (instance.colFr?.length) params.set('colFr', instance.colFr.join(','));
+ params.set('slots', JSON.stringify(instance.slots));
+ return `widget.html?${params.toString()}`;
+}
+
+export async function openWidgetWindow(instance: FloatingWidgetInstance): Promise {
+ const label = `widget-${instance.id}`;
+ const existing = widgetWindows.get(instance.id) ?? (await WebviewWindow.getByLabel(label));
+ if (existing) {
+ await existing.setFocus();
+ widgetWindows.set(instance.id, existing);
+ return;
+ }
+
+ const win = new WebviewWindow(label, {
+ url: widgetUrl(instance),
+ title: 'GoldenChart Widget',
+ width: instance.width,
+ height: instance.height,
+ minWidth: 320,
+ minHeight: 240,
+ decorations: true,
+ resizable: true,
+ center: false,
+ x: instance.position.x,
+ y: instance.position.y,
+ });
+
+ widgetWindows.set(instance.id, win);
+
+ void win.once('tauri://destroyed', () => {
+ widgetWindows.delete(instance.id);
+ });
+}
+
+export async function closeWidgetWindow(id: string): Promise {
+ const label = `widget-${id}`;
+ const win = widgetWindows.get(id) ?? (await WebviewWindow.getByLabel(label));
+ if (win) {
+ await win.close();
+ widgetWindows.delete(id);
+ }
+}
+
+export async function focusWidgetWindow(id: string): Promise {
+ const label = `widget-${id}`;
+ const win = widgetWindows.get(id) ?? (await WebviewWindow.getByLabel(label));
+ if (win) await win.setFocus();
+}
diff --git a/desktop/src/ensureDesktopApiBase.ts b/desktop/src/ensureDesktopApiBase.ts
new file mode 100644
index 0000000..4af53d3
--- /dev/null
+++ b/desktop/src/ensureDesktopApiBase.ts
@@ -0,0 +1,6 @@
+import { DESKTOP_API_BASE, setApiBase } from '@goldenchart/shared';
+
+/** Tauri desktop — 항상 https exdev (localhost·http 금지) */
+export function ensureDesktopApiBase(): void {
+ setApiBase(DESKTOP_API_BASE);
+}
diff --git a/desktop/src/forceServerApi.ts b/desktop/src/forceServerApi.ts
new file mode 100644
index 0000000..3d0f2ac
--- /dev/null
+++ b/desktop/src/forceServerApi.ts
@@ -0,0 +1,4 @@
+import { ensureDesktopApiBase } from './ensureDesktopApiBase';
+
+/** App·backendApi import 전 API base 고정 (localhost 금지) */
+ensureDesktopApiBase();
diff --git a/desktop/src/main.tsx b/desktop/src/main.tsx
new file mode 100644
index 0000000..4f4d354
--- /dev/null
+++ b/desktop/src/main.tsx
@@ -0,0 +1,30 @@
+import './forceServerApi';
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { initStorage, refreshApiBaseFromStorage } from '@goldenchart/shared';
+import { installDesktopBridge } from './bridge/installDesktopBridge';
+import { ensureDesktopApiBase } from './ensureDesktopApiBase';
+import App from '@frontend/App';
+
+installDesktopBridge();
+
+/** 시작 시 백그라운드 업데이트 확인 (실패 무시) */
+void import('./bridge/updater').then(({ checkForUpdates }) => {
+ window.setTimeout(() => {
+ void checkForUpdates().catch(() => { /* offline 등 */ });
+ }, 8000);
+});
+
+async function bootstrap() {
+ await initStorage();
+ ensureDesktopApiBase();
+ refreshApiBaseFromStorage();
+
+ ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+ );
+}
+
+void bootstrap();
diff --git a/desktop/src/vite-env.d.ts b/desktop/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/desktop/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/desktop/src/widget-main.tsx b/desktop/src/widget-main.tsx
new file mode 100644
index 0000000..22a1ec6
--- /dev/null
+++ b/desktop/src/widget-main.tsx
@@ -0,0 +1,158 @@
+import './forceServerApi';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import ReactDOM from 'react-dom/client';
+import { getCurrentWindow } from '@tauri-apps/api/window';
+import { initStorage, refreshApiBaseFromStorage } from '@goldenchart/shared';
+import { ensureDesktopApiBase } from './ensureDesktopApiBase';
+import FloatingWidgetWindow from '@frontend/components/floatingWidgets/FloatingWidgetWindow';
+import {
+ defaultGridFr,
+ type FloatingWidgetInstance,
+} from '@frontend/types/floatingWidget';
+import type { Theme } from '@frontend/types';
+import type { WidgetSlot } from '@frontend/types/widgetDashboard';
+import { useMarketTicker } from '@frontend/hooks/useMarketTicker';
+import {
+ loadPaperSummary,
+ loadPaperTrades,
+ loadStrategies,
+ type PaperSummaryDto,
+ type PaperTradeDto,
+ type StrategyDto,
+} from '@frontend/utils/backendApi';
+import { PAPER_TRADES_CHANGED_EVENT } from '@frontend/utils/paperTradeEvents';
+import { WidgetDashboardProvider } from '@frontend/widgets/WidgetDashboardContext';
+import { readDesktopSync, subscribeDesktopSync } from '@frontend/utils/desktopSync';
+import '@frontend/styles/paperDashboard.css';
+import '@frontend/styles/widgetDashboard.css';
+import '@frontend/styles/floatingWidget.css';
+
+function parseInstanceFromUrl(): FloatingWidgetInstance | null {
+ const params = new URLSearchParams(window.location.search);
+ const id = params.get('id');
+ if (!id) return null;
+ const rows = Number(params.get('rows') ?? 1);
+ const cols = Number(params.get('cols') ?? 1);
+ const width = Number(params.get('width') ?? 640);
+ const height = Number(params.get('height') ?? 480);
+ let slots: WidgetSlot[] = [];
+ try {
+ slots = JSON.parse(params.get('slots') ?? '[]') as WidgetSlot[];
+ } catch {
+ slots = [];
+ }
+ const rowFrRaw = params.get('rowFr');
+ const colFrRaw = params.get('colFr');
+ const rowFr = rowFrRaw ? rowFrRaw.split(',').map(Number) : defaultGridFr(rows);
+ const colFr = colFrRaw ? colFrRaw.split(',').map(Number) : defaultGridFr(cols);
+
+ return {
+ id,
+ rows,
+ cols,
+ slots,
+ rowFr,
+ colFr,
+ position: { x: 0, y: 0 },
+ width,
+ height,
+ zIndex: 1,
+ };
+}
+
+const WidgetApp: React.FC = () => {
+ const initial = useMemo(() => parseInstanceFromUrl(), []);
+ const [instance, setInstance] = useState(initial);
+ const sync = readDesktopSync();
+ const [theme, setTheme] = useState((sync?.theme as Theme | undefined) ?? 'dark');
+ const [defaultMarket, setDefaultMarket] = useState(sync?.selectedMarket ?? 'KRW-BTC');
+
+ const { tickers, marketInfos, loading: marketLoading, usdRate } = useMarketTicker(
+ useMemo(() => ({ enabled: true, loadFull: true }), []),
+ );
+ const [strategies, setStrategies] = useState([]);
+ const [summary, setSummary] = useState(null);
+ const [trades, setTrades] = useState([]);
+
+ const refreshPaperData = useCallback(async () => {
+ try {
+ const [sum, tr] = await Promise.all([loadPaperSummary(), loadPaperTrades()]);
+ setSummary(sum);
+ setTrades(tr ?? []);
+ } catch { /* ignore */ }
+ }, []);
+
+ useEffect(() => {
+ void loadStrategies().then(list => setStrategies(list ?? []));
+ void refreshPaperData();
+ }, [refreshPaperData]);
+
+ useEffect(() => {
+ const onChanged = () => { void refreshPaperData(); };
+ window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
+ return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
+ }, [refreshPaperData]);
+
+ useEffect(() => {
+ return subscribeDesktopSync(state => {
+ if (state.theme) setTheme(state.theme as Theme);
+ if (state.selectedMarket) setDefaultMarket(state.selectedMarket);
+ });
+ }, []);
+
+ useEffect(() => {
+ document.documentElement.setAttribute('data-theme', theme);
+ }, [theme]);
+
+ const handleClose = useCallback(() => {
+ void getCurrentWindow().close();
+ }, []);
+
+ if (!instance) {
+ return 위젯 정보를 불러올 수 없습니다.
;
+ }
+
+ return (
+ { void refreshPaperData(); }}
+ paperTradingEnabled
+ paperAutoTradeEnabled={false}
+ chartRealtimeSource="BACKEND_STOMP"
+ >
+
+ {}}
+ onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))}
+ onUpdateSize={(width, height) => setInstance(prev => (prev ? { ...prev, width, height } : prev))}
+ onUpdateGridFr={(rowFr, colFr) => setInstance(prev => (prev ? { ...prev, rowFr, colFr } : prev))}
+ />
+
+
+ );
+};
+
+async function bootstrap() {
+ await initStorage();
+ ensureDesktopApiBase();
+ refreshApiBaseFromStorage();
+
+ ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+ );
+}
+
+void bootstrap();
diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json
new file mode 100644
index 0000000..4bfc44e
--- /dev/null
+++ b/desktop/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "paths": {
+ "@goldenchart/shared": ["../packages/shared/src/index.ts"],
+ "@goldenchart/shared/*": ["../packages/shared/src/*"],
+ "@frontend/*": ["../frontend/src/*"]
+ }
+ },
+ "include": ["src", "../frontend/src", "../packages/shared/src"]
+}
diff --git a/desktop/tsconfig.node.json b/desktop/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/desktop/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/desktop/updater.pub.example b/desktop/updater.pub.example
new file mode 100644
index 0000000..0ffdc2a
--- /dev/null
+++ b/desktop/updater.pub.example
@@ -0,0 +1,5 @@
+# Tauri updater public key (minisign, 한 줄)
+# 생성: ./scripts/setup-desktop-updater-keys.sh
+# 실제 키는 desktop/updater.pub (git 커밋) — private key는 ~/.tauri/goldenchart.key (커밋 금지)
+
+REPLACE_WITH_TAURI_SIGNER_PUBKEY
diff --git a/desktop/vite.config.ts b/desktop/vite.config.ts
new file mode 100644
index 0000000..5a3253d
--- /dev/null
+++ b/desktop/vite.config.ts
@@ -0,0 +1,84 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const frontendRoot = path.resolve(__dirname, '../frontend/src');
+const sharedRoot = path.resolve(__dirname, '../packages/shared/src');
+
+const host = process.env.TAURI_DEV_HOST;
+
+export default defineConfig({
+ /** Tauri 패키지 앱 — 절대 경로(/assets) 사용 시 빈 화면 */
+ base: './',
+ /** sockjs-client 등 Node 전역 참조 — Tauri WebView에는 global 없음 */
+ define: {
+ global: 'globalThis',
+ /** desktop은 항상 exdev 서버 (localhost 금지) */
+ 'import.meta.env.VITE_API_BASE_URL': JSON.stringify('https://exdev.co.kr/api'),
+ __DESKTOP_CLIENT__: 'true',
+ },
+ envDir: __dirname,
+ plugins: [react()],
+ resolve: {
+ dedupe: ['react', 'react-dom', 'lightweight-charts'],
+ alias: {
+ '@goldenchart/shared': sharedRoot,
+ '@frontend': frontendRoot,
+ '@frontend/utils/backendApi': path.resolve(sharedRoot, 'api/backendApi.ts'),
+ [path.resolve(frontendRoot, 'utils/backendApi.ts')]: path.resolve(
+ sharedRoot,
+ 'api/backendApi.ts',
+ ),
+ },
+ },
+ optimizeDeps: {
+ include: ['react', 'react-dom', '@stomp/stompjs', 'sockjs-client'],
+ esbuildOptions: {
+ define: {
+ global: 'globalThis',
+ },
+ },
+ },
+ build: {
+ outDir: 'dist',
+ sourcemap: true,
+ rollupOptions: {
+ input: {
+ main: path.resolve(__dirname, 'index.html'),
+ widget: path.resolve(__dirname, 'widget.html'),
+ },
+ },
+ },
+ clearScreen: false,
+ server: {
+ port: 5175,
+ strictPort: true,
+ host: host || false,
+ hmr: host
+ ? { protocol: 'ws', host, port: 5176 }
+ : undefined,
+ watch: {
+ ignored: ['**/src-tauri/**'],
+ },
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ ws: true,
+ },
+ '/upbit-api': {
+ target: 'https://api.upbit.com',
+ changeOrigin: true,
+ rewrite: p => p.replace(/^\/upbit-api/, ''),
+ },
+ '/upbit-ws': {
+ target: 'wss://api.upbit.com',
+ changeOrigin: true,
+ ws: true,
+ rewrite: () => '/websocket/v1',
+ },
+ },
+ },
+});
diff --git a/desktop/widget.html b/desktop/widget.html
new file mode 100644
index 0000000..8eabebc
--- /dev/null
+++ b/desktop/widget.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ GoldenChart Widget
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/desktop-pilot.md b/docs/desktop-pilot.md
new file mode 100644
index 0000000..9ea350c
--- /dev/null
+++ b/docs/desktop-pilot.md
@@ -0,0 +1,111 @@
+# GoldenChart Desktop — 내부 파일럿 배포·운영 가이드 (10명 이내)
+
+## 1. 서버 최초 설정 (macOS Jenkins, 1회)
+
+```bash
+cd /Users/aidev/apps/goldenChart # WORK_TREE
+
+# Rust · NSIS · cargo-xwin
+./scripts/install-desktop-build-deps.sh
+
+# updater 서명 키 (private → ~/.tauri/, public → desktop/updater.pub)
+./scripts/setup-desktop-updater-keys.sh
+git add desktop/updater.pub desktop/src-tauri/tauri.conf.json
+git commit -m "chore(desktop): add updater public key"
+
+# Jenkins Job 등록
+./scripts/server-install-desktop-jenkins.sh
+```
+
+Jenkins Credential (Secret file): `tauri-signer-key` → `/Users/aidev/.tauri/goldenchart.key`
+
+Global PATH에 추가:
+
+```
+/opt/homebrew/opt/llvm/bin
+/opt/homebrew/bin
+$HOME/.cargo/bin
+```
+
+## 2. Jenkins Job: goldenChart-Desktop-Pipeline
+
+| 항목 | 값 |
+|------|-----|
+| SCM | `file:///Volumes/ADATA/git/goldenChart.git` main |
+| 스케줄 | 30분마다 SCM poll (또는 수동) |
+| 빌드 | `scripts/jenkins-desktop-pipeline.sh` |
+| Artifact | `dist-desktop/**` |
+
+환경 변수 (선택):
+
+| 변수 | 설명 |
+|------|------|
+| `TAURI_SIGNER_KEY` | private key 경로 |
+| `DESKTOP_DEPLOY_WEB=1` | 빌드 후 frontend Docker 재배포 |
+| `BUMP_VERSION=patch` | 빌드 시 버전 bump |
+| `DESKTOP_UPDATE_BASE_URL` | latest.json URL base |
+
+수동 실행:
+
+```bash
+./scripts/jenkins-desktop-pipeline.sh
+# 또는
+./scripts/build-desktop.sh --all
+./scripts/publish-desktop-update.sh
+```
+
+## 3. 사용자 설치
+
+### macOS
+
+1. Jenkins → `dist-desktop/*.dmg` 또는 updates URL에서 다운로드
+2. DMG → Applications
+3. 최초 실행: **시스템 설정 → 개인정보 보호 → 확인 없이 열기**
+
+### Windows
+
+1. `*-setup.exe` (NSIS) 다운로드
+2. SmartScreen → **추가 정보 → 실행**
+
+## 4. 앱 기능
+
+- **서버**: `http://exdev.co.kr/api` (웹과 동일 계정·DB)
+- **위젯**: OS 별도 창 (`WebviewWindow`)
+- **알림**: 창 닫기 → 트레이 상주, STOMP + OS 알림
+- **업데이트**: 설정 → 일반 → PC 앱 → **업데이트 확인**
+
+## 5. 업데이트 배포 흐름
+
+```
+git push main
+ → Jenkins goldenChart-Desktop-Pipeline
+ → build-desktop.sh (dmg + exe + updater bundles)
+ → publish-desktop-update.sh (sign + latest.json)
+ → frontend/public/desktop/updates/
+ → (DESKTOP_DEPLOY_WEB=1) nginx 재배포
+ → 클라이언트 앱 내 업데이트
+```
+
+## 6. Smoke Test
+
+```bash
+npm run smoke:desktop
+```
+
+## 로컬 개발 (Mac)
+
+```bash
+npm install
+
+# 최초 1회 — Homebrew(~/homebrew) + Rust + NSIS/LLVM
+npm run install:desktop:deps -- --mac-only # macOS 빌드만 (빠름)
+# npm run install:desktop:deps # Windows 크로스 빌드 포함
+
+source ~/.zprofile # PATH 적용
+npm run dev:desktop
+npm run build:desktop:mac
+```
+
+산출물: `desktop/src-tauri/target/release/bundle/dmg/GoldenChart_*.dmg`
+
+관리자 권한이 있으면 `/opt/homebrew` 정식 설치를 권장합니다 (bottle 사용, LLVM 빌드 시간 단축).
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
index 96b3e20..f468d6c 100644
--- a/frontend/nginx.conf
+++ b/frontend/nginx.conf
@@ -141,6 +141,13 @@ server {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
+ # GoldenChart Desktop updater manifest + bundles
+ location /desktop/updates/ {
+ alias /usr/share/nginx/html/desktop/updates/;
+ add_header Cache-Control "no-cache, no-store, must-revalidate";
+ default_type application/json;
+ }
+
# gzip 압축
gzip on;
gzip_vary on;
diff --git a/frontend/public/desktop/updates/.gitkeep b/frontend/public/desktop/updates/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/public/desktop/updates/latest.json b/frontend/public/desktop/updates/latest.json
new file mode 100644
index 0000000..cc93840
--- /dev/null
+++ b/frontend/public/desktop/updates/latest.json
@@ -0,0 +1,6 @@
+{
+ "version": "0.1.0",
+ "notes": "Initial desktop release placeholder",
+ "pub_date": "2026-06-11T00:00:00Z",
+ "platforms": {}
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 2d00f65..2416ac3 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -35,6 +35,8 @@ import { clearAdminUnlock } from './utils/adminUnlock';
import type { LoginResponse } from './utils/backendApi';
import { invalidateAppSettingsCache } from './hooks/useAppSettings';
import { invalidateIndicatorSettingsCache } from './hooks/useIndicatorSettings';
+import { isDesktop } from './utils/platform';
+import { writeDesktopSync } from './utils/desktopSync';
const LAST_MENU_KEY = 'gc_last_menu';
const CHART_ONLY_INITIAL: ReadonlySet = new Set(['dashboard', 'settings', 'verification-board']);
@@ -171,6 +173,11 @@ function AppMainContent({
const [floatingWidgetCount, setFloatingWidgetCount] = useState(0);
const showFloatingWidgets = canMenu('widget-dashboard');
+ useEffect(() => {
+ if (!isDesktop()) return;
+ writeDesktopSync({ theme, selectedMarket: symbol });
+ }, [theme, symbol]);
+
const handleFullscreen = () => {
if (!document.fullscreenElement) document.documentElement.requestFullscreen().catch(() => {});
else document.exitFullscreen().catch(() => {});
diff --git a/frontend/src/components/DesktopUpdatePanel.tsx b/frontend/src/components/DesktopUpdatePanel.tsx
new file mode 100644
index 0000000..e71f517
--- /dev/null
+++ b/frontend/src/components/DesktopUpdatePanel.tsx
@@ -0,0 +1,62 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { isDesktop } from '../utils/platform';
+import {
+ checkDesktopUpdates,
+ getDesktopAppVersion,
+} from '../utils/desktopBridge';
+
+/** PC(Tauri) 앱 — 버전 표시 및 업데이트 확인 */
+const DesktopUpdatePanel: React.FC = () => {
+ const [version, setVersion] = useState('—');
+ const [status, setStatus] = useState('');
+ const [busy, setBusy] = useState(false);
+
+ useEffect(() => {
+ if (!isDesktop()) return;
+ void getDesktopAppVersion().then(v => {
+ if (v) setVersion(v);
+ });
+ }, []);
+
+ const handleCheck = useCallback(async () => {
+ setBusy(true);
+ setStatus('업데이트 확인 중…');
+ try {
+ const res = await checkDesktopUpdates();
+ setStatus(res.message ?? (res.available ? `새 버전 ${res.version}` : '최신 버전입니다.'));
+ } finally {
+ setBusy(false);
+ }
+ }, []);
+
+ if (!isDesktop()) return null;
+
+ return (
+
+
PC 앱 (GoldenChart Desktop)
+
+
+
현재 버전
+
설치된 데스크톱 클라이언트 버전입니다.
+
+
+ {version}
+
+
+
+
+
업데이트
+
서버에서 새 버전을 확인하고 다운로드합니다.
+
+
+
+ {status && {status}}
+
+
+
+ );
+};
+
+export default DesktopUpdatePanel;
diff --git a/frontend/src/components/SettingsPage.tsx b/frontend/src/components/SettingsPage.tsx
index e60c72f..2f85b69 100644
--- a/frontend/src/components/SettingsPage.tsx
+++ b/frontend/src/components/SettingsPage.tsx
@@ -17,6 +17,7 @@ import type { IndicatorConfig } from '../types';
import type { PlotDef, HLineDef } from '../utils/indicatorRegistry';
import type { IchimokuCloudColors } from '../utils/ichimokuConfig';
import ChartTimeFormatPicker from './ChartTimeFormatPicker';
+import DesktopUpdatePanel from './DesktopUpdatePanel';
import {
TRADE_ALERT_SOUND_OPTIONS,
normalizeTradeAlertSoundId,
@@ -911,6 +912,8 @@ const GeneralPanel: React.FC<{
+
+
>
);
};
diff --git a/frontend/src/components/floatingWidgets/FloatingWidgetLayer.tsx b/frontend/src/components/floatingWidgets/FloatingWidgetLayer.tsx
index 4eb1b70..32bbc30 100644
--- a/frontend/src/components/floatingWidgets/FloatingWidgetLayer.tsx
+++ b/frontend/src/components/floatingWidgets/FloatingWidgetLayer.tsx
@@ -1,6 +1,3 @@
-/**
- * 플로팅 위젯 레이어 — 메뉴바에서 다중 팝업 위젯 실행
- */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import type { Theme } from '../../types';
@@ -24,6 +21,9 @@ import {
import type { WidgetSlot } from '../../types/widgetDashboard';
import FloatingWidgetLayoutPicker from './FloatingWidgetLayoutPicker';
import FloatingWidgetWindow from './FloatingWidgetWindow';
+import { isDesktop } from '../../utils/platform';
+import { getDesktopBridge } from '../../utils/desktopBridge';
+import { writeDesktopSync } from '../../utils/desktopSync';
import '../../styles/paperDashboard.css';
import '../../styles/widgetDashboard.css';
import '../../styles/floatingWidget.css';
@@ -40,6 +40,7 @@ interface Props {
onPaperOrderFilled?: () => void;
chartRealtimeSource?: ChartRealtimeSource;
onInstancesChange?: (count: number) => void;
+ selectedStrategyId?: number | null;
}
const FloatingWidgetLayer: React.FC = ({
@@ -52,7 +53,9 @@ const FloatingWidgetLayer: React.FC = ({
onPaperOrderFilled,
chartRealtimeSource = 'BACKEND_STOMP',
onInstancesChange,
+ selectedStrategyId = null,
}) => {
+ const desktopMode = isDesktop();
const [instances, setInstances] = useState([]);
const [focusedId, setFocusedId] = useState(null);
const zCounterRef = useRef(BASE_Z + 100);
@@ -83,14 +86,28 @@ const FloatingWidgetLayer: React.FC = ({
return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
}, [refreshPaperData]);
+ useEffect(() => {
+ if (!desktopMode) return;
+ writeDesktopSync({
+ selectedMarket: defaultMarket,
+ selectedStrategyId,
+ theme,
+ });
+ }, [desktopMode, defaultMarket, selectedStrategyId, theme]);
+
const bringToFront = useCallback((id: string) => {
+ if (desktopMode) {
+ void getDesktopBridge()?.focusWidgetWindow(id);
+ setFocusedId(id);
+ return;
+ }
zCounterRef.current += 1;
const nextZ = zCounterRef.current;
setFocusedId(id);
setInstances(prev => prev.map(inst =>
inst.id === id ? { ...inst, zIndex: nextZ } : inst,
));
- }, []);
+ }, [desktopMode]);
const handleLayoutSelect = useCallback((preset: FloatingWidgetLayoutPreset) => {
zCounterRef.current += 1;
@@ -102,14 +119,25 @@ const FloatingWidgetLayer: React.FC = ({
zCounterRef.current,
);
inst.zIndex = zCounterRef.current;
+
+ if (desktopMode) {
+ void getDesktopBridge()?.openWidgetWindow(inst);
+ setInstances(prev => [...prev, inst]);
+ setFocusedId(inst.id);
+ return;
+ }
+
setInstances(prev => [...prev, inst]);
setFocusedId(inst.id);
- }, [instances.length]);
+ }, [instances.length, desktopMode]);
const handleClose = useCallback((id: string) => {
+ if (desktopMode) {
+ void getDesktopBridge()?.closeWidgetWindow(id);
+ }
setInstances(prev => prev.filter(i => i.id !== id));
setFocusedId(prev => (prev === id ? null : prev));
- }, []);
+ }, [desktopMode]);
const handleUpdateSlots = useCallback((id: string, slots: WidgetSlot[]) => {
setInstances(prev => prev.map(inst => (inst.id === id ? { ...inst, slots } : inst)));
@@ -122,9 +150,9 @@ const FloatingWidgetLayer: React.FC = ({
}, []);
const handleUpdateGridFr = useCallback((id: string, rowFr: number[], colFr: number[]) => {
- setInstances(prev => prev.map(inst => (
- inst.id === id ? { ...inst, rowFr, colFr } : inst
- )));
+ setInstances(prev => prev.map(inst =>
+ inst.id === id ? { ...inst, rowFr, colFr } : inst,
+ ));
}, []);
useEffect(() => {
@@ -133,6 +161,42 @@ const FloatingWidgetLayer: React.FC = ({
if (instances.length === 0 && !layoutPickerOpen) return null;
+ const picker = (
+ onLayoutPickerOpenChange(false)}
+ onSelect={handleLayoutSelect}
+ />
+ );
+
+ if (desktopMode) {
+ return (
+ { void refreshPaperData(); }}
+ paperTradingEnabled={paperTradingEnabled}
+ paperAutoTradeEnabled={paperAutoTradeEnabled}
+ onPaperOrderFilled={onPaperOrderFilled}
+ chartRealtimeSource={chartRealtimeSource}
+ >
+ {layoutPickerOpen && createPortal(
+
+ {picker}
+
,
+ document.body,
+ )}
+
+ );
+ }
+
return (
= ({
/>
))}
- onLayoutPickerOpenChange(false)}
- onSelect={handleLayoutSelect}
- />
+ {picker}
,
document.body,
)}
diff --git a/frontend/src/contexts/TradeNotificationContext.tsx b/frontend/src/contexts/TradeNotificationContext.tsx
index 8311f43..d8990fe 100644
--- a/frontend/src/contexts/TradeNotificationContext.tsx
+++ b/frontend/src/contexts/TradeNotificationContext.tsx
@@ -27,6 +27,8 @@ import {
type TradeAlertSoundId,
} from '../utils/tradeAlertSound';
import { normalizeStrategyId } from '../utils/resolveNotificationStrategy';
+import { showDesktopNativeNotification } from '../utils/desktopBridge';
+import { getKoreanName } from '../utils/marketNameCache';
import { getUiPreferences, patchUiPreferences } from '../utils/uiPreferencesDb';
import { useAppSettings } from '../hooks/useAppSettings';
@@ -428,6 +430,13 @@ export const TradeNotificationProvider: React.FC = ({
const rest = prev.filter(n => n.id !== id);
return [toastItem, ...rest].slice(0, MAX_TOAST_QUEUE);
});
+
+ const sideLabel = signal.signalType === 'BUY' ? '매수' : '매도';
+ const marketLabel = getKoreanName(signal.market);
+ void showDesktopNativeNotification(
+ `GoldenChart ${sideLabel} 시그널`,
+ `${marketLabel} · ₩${Math.round(signal.price ?? 0).toLocaleString()}`,
+ );
}, [popupEnabled, soundEnabled, alertSoundId]);
const dismissToast = useCallback((id: string) => {
diff --git a/frontend/src/styles/floatingWidget.css b/frontend/src/styles/floatingWidget.css
index 0fe0a99..294ca13 100644
--- a/frontend/src/styles/floatingWidget.css
+++ b/frontend/src/styles/floatingWidget.css
@@ -593,3 +593,26 @@
position: relative;
display: inline-flex;
}
+
+/* Tauri 네이티브 위젯 창 */
+.fw-layer--picker-only {
+ pointer-events: auto;
+}
+
+.fw-widget-native-root {
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.fw-widget-native-root .fw-window {
+ position: static !important;
+ width: 100% !important;
+ height: 100% !important;
+ max-width: none !important;
+ max-height: none !important;
+ box-shadow: none;
+ border: none;
+}
diff --git a/frontend/src/utils/desktopBridge.ts b/frontend/src/utils/desktopBridge.ts
new file mode 100644
index 0000000..83f0b3a
--- /dev/null
+++ b/frontend/src/utils/desktopBridge.ts
@@ -0,0 +1,47 @@
+import { isDesktop } from './platform';
+
+export interface DesktopBridge {
+ openWidgetWindow: (instance: import('../types/floatingWidget').FloatingWidgetInstance) => Promise;
+ closeWidgetWindow: (id: string) => Promise;
+ focusWidgetWindow: (id: string) => Promise;
+ showNativeNotification: (title: string, body: string) => Promise;
+ checkForUpdates: () => Promise<{ available: boolean; version?: string; message?: string }>;
+ getAppVersion: () => Promise;
+}
+
+declare global {
+ interface Window {
+ __goldenDesktopBridge?: DesktopBridge;
+ }
+}
+
+export function getDesktopBridge(): DesktopBridge | null {
+ if (!isDesktop()) return null;
+ return window.__goldenDesktopBridge ?? null;
+}
+
+export async function showDesktopNativeNotification(title: string, body: string): Promise {
+ const bridge = getDesktopBridge();
+ if (!bridge) return;
+ try {
+ await bridge.showNativeNotification(title, body);
+ } catch {
+ /* ignore */
+ }
+}
+
+export async function checkDesktopUpdates(): Promise<{ available: boolean; version?: string; message?: string }> {
+ const bridge = getDesktopBridge();
+ if (!bridge) return { available: false, message: '데스크톱 앱에서만 사용 가능합니다.' };
+ return bridge.checkForUpdates();
+}
+
+export async function getDesktopAppVersion(): Promise {
+ const bridge = getDesktopBridge();
+ if (!bridge) return null;
+ try {
+ return await bridge.getAppVersion();
+ } catch {
+ return null;
+ }
+}
diff --git a/frontend/src/utils/desktopSync.ts b/frontend/src/utils/desktopSync.ts
new file mode 100644
index 0000000..76e68c1
--- /dev/null
+++ b/frontend/src/utils/desktopSync.ts
@@ -0,0 +1,43 @@
+/** 데스크톱 메인↔위젯 창 상태 동기화 (localStorage) */
+export const DESKTOP_SYNC_KEY = 'gc_desktop_sync';
+
+export interface DesktopSyncState {
+ selectedMarket?: string;
+ selectedStrategyId?: number | null;
+ theme?: string;
+ updatedAt: number;
+}
+
+export function readDesktopSync(): DesktopSyncState | null {
+ try {
+ const raw = localStorage.getItem(DESKTOP_SYNC_KEY);
+ if (!raw) return null;
+ return JSON.parse(raw) as DesktopSyncState;
+ } catch {
+ return null;
+ }
+}
+
+export function writeDesktopSync(partial: Omit): void {
+ const prev = readDesktopSync() ?? { updatedAt: 0 };
+ const next: DesktopSyncState = {
+ ...prev,
+ ...partial,
+ updatedAt: Date.now(),
+ };
+ try {
+ localStorage.setItem(DESKTOP_SYNC_KEY, JSON.stringify(next));
+ } catch {
+ /* ignore */
+ }
+}
+
+export function subscribeDesktopSync(cb: (state: DesktopSyncState) => void): () => void {
+ const onStorage = (e: StorageEvent) => {
+ if (e.key !== DESKTOP_SYNC_KEY) return;
+ const state = readDesktopSync();
+ if (state) cb(state);
+ };
+ window.addEventListener('storage', onStorage);
+ return () => window.removeEventListener('storage', onStorage);
+}
diff --git a/frontend/src/utils/platform.ts b/frontend/src/utils/platform.ts
new file mode 100644
index 0000000..1b00330
--- /dev/null
+++ b/frontend/src/utils/platform.ts
@@ -0,0 +1,26 @@
+/** 런타임 플랫폼 — web · desktop(Tauri) · mobile */
+export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
+
+export function isDesktop(): boolean {
+ return typeof window !== 'undefined' && '__TAURI__' in window;
+}
+
+export function isMobileCapacitor(): boolean {
+ if (typeof window === 'undefined') return false;
+ try {
+ const cap = (window as Window & { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor;
+ return cap?.isNativePlatform?.() === true;
+ } catch {
+ return false;
+ }
+}
+
+export function getRuntimePlatform(): RuntimePlatform {
+ if (isDesktop()) return 'desktop';
+ if (isMobileCapacitor()) return 'mobile';
+ return 'web';
+}
+
+export function isWeb(): boolean {
+ return getRuntimePlatform() === 'web';
+}
diff --git a/package-lock.json b/package-lock.json
index 0de6730..2550f07 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,8 @@
"workspaces": [
"packages/shared",
"app",
- "frontend"
+ "frontend",
+ "desktop"
]
},
"app": {
@@ -41,6 +42,38 @@
"vite": "^5.4.0"
}
},
+ "desktop": {
+ "name": "@goldenchart/desktop",
+ "version": "0.1.0",
+ "dependencies": {
+ "@goldenchart/shared": "*",
+ "@stomp/stompjs": "^7.3.0",
+ "@tanstack/react-virtual": "^3.14.2",
+ "@tauri-apps/api": "^2",
+ "@tauri-apps/plugin-notification": "^2",
+ "@tauri-apps/plugin-opener": "^2",
+ "@tauri-apps/plugin-process": "^2",
+ "@tauri-apps/plugin-updater": "^2",
+ "@xyflow/react": "^12.10.2",
+ "lightweight-charts": "^5.2.0",
+ "lightweight-charts-indicators": "^0.4.1",
+ "oakscriptjs": "^0.2.8",
+ "qrcode": "^1.5.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "sockjs-client": "^1.6.1"
+ },
+ "devDependencies": {
+ "@tauri-apps/cli": "^2",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^18.3.1",
+ "@types/react-dom": "^18.3.1",
+ "@types/sockjs-client": "^1.5.4",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "^5.5.4",
+ "vite": "^5.4.0"
+ }
+ },
"frontend": {
"name": "react-trading-chart",
"version": "0.1.0",
@@ -67,23 +100,6 @@
"vite": "^5.4.0"
}
},
- "frontend/node_modules/lightweight-charts-indicators": {
- "version": "0.4.1",
- "license": "MIT",
- "peerDependencies": {
- "oakscriptjs": "^0.2.8"
- }
- },
- "frontend/node_modules/oakscriptjs": {
- "version": "0.2.8",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "lightweight-charts": "^5.0.0"
- }
- },
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
@@ -886,6 +902,10 @@
"resolved": "app",
"link": true
},
+ "node_modules/@goldenchart/desktop": {
+ "resolved": "desktop",
+ "link": true
+ },
"node_modules/@goldenchart/shared": {
"resolved": "packages/shared",
"link": true
@@ -1530,6 +1550,284 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tauri-apps/api": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz",
+ "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==",
+ "license": "Apache-2.0 OR MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ }
+ },
+ "node_modules/@tauri-apps/cli": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz",
+ "integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==",
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "bin": {
+ "tauri": "tauri.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ },
+ "optionalDependencies": {
+ "@tauri-apps/cli-darwin-arm64": "2.11.2",
+ "@tauri-apps/cli-darwin-x64": "2.11.2",
+ "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2",
+ "@tauri-apps/cli-linux-arm64-gnu": "2.11.2",
+ "@tauri-apps/cli-linux-arm64-musl": "2.11.2",
+ "@tauri-apps/cli-linux-riscv64-gnu": "2.11.2",
+ "@tauri-apps/cli-linux-x64-gnu": "2.11.2",
+ "@tauri-apps/cli-linux-x64-musl": "2.11.2",
+ "@tauri-apps/cli-win32-arm64-msvc": "2.11.2",
+ "@tauri-apps/cli-win32-ia32-msvc": "2.11.2",
+ "@tauri-apps/cli-win32-x64-msvc": "2.11.2"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-arm64": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz",
+ "integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-x64": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz",
+ "integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz",
+ "integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz",
+ "integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-musl": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz",
+ "integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz",
+ "integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-gnu": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz",
+ "integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-musl": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz",
+ "integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-arm64-msvc": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz",
+ "integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz",
+ "integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-x64-msvc": {
+ "version": "2.11.2",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz",
+ "integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/plugin-notification": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.3.3.tgz",
+ "integrity": "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.8.0"
+ }
+ },
+ "node_modules/@tauri-apps/plugin-opener": {
+ "version": "2.5.4",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz",
+ "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.11.0"
+ }
+ },
+ "node_modules/@tauri-apps/plugin-process": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz",
+ "integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.8.0"
+ }
+ },
+ "node_modules/@tauri-apps/plugin-updater": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.1.tgz",
+ "integrity": "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@tauri-apps/api": "^2.10.1"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2550,6 +2848,15 @@
"fancy-canvas": "2.1.0"
}
},
+ "node_modules/lightweight-charts-indicators": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/lightweight-charts-indicators/-/lightweight-charts-indicators-0.4.2.tgz",
+ "integrity": "sha512-UITNFEaKYqvRPm+67Mz7nXMZIE+dilqdeQsIcFqwe3I4n0U3BQm95/OKSlWIpBqiqftJff9iq3i4rtOg/p2tLQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "oakscriptjs": "^0.2.8"
+ }
+ },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -2684,6 +2991,18 @@
"node": ">=18"
}
},
+ "node_modules/oakscriptjs": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/oakscriptjs/-/oakscriptjs-0.2.8.tgz",
+ "integrity": "sha512-SRLR1QpnO3k8MNFzT5ZLEauWeULAcDTJ+0L0JXtshMvfDc5f8ezXVwgQ3D9Lv2g4aiUbJNmK+sNuZ8NKeC1I7Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "lightweight-charts": "^5.0.0"
+ }
+ },
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
diff --git a/package.json b/package.json
index 7a266ae..f4c3a78 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,19 @@
"workspaces": [
"packages/shared",
"app",
- "frontend"
+ "frontend",
+ "desktop"
],
"scripts": {
"build:frontend": "npm run build -w react-trading-chart",
+ "dev:desktop": "npm run tauri:dev -w @goldenchart/desktop",
+ "build:desktop": "npm run build -w @goldenchart/desktop && npm run tauri:build -w @goldenchart/desktop",
+ "build:desktop:mac": "npm run build -w @goldenchart/desktop && npm run tauri:build:mac -w @goldenchart/desktop",
+ "build:desktop:win": "npm run build -w @goldenchart/desktop && npm run tauri:build:win -w @goldenchart/desktop",
+ "install:desktop:deps": "./scripts/install-desktop-build-deps.sh",
+ "setup:desktop:keys": "./scripts/setup-desktop-updater-keys.sh",
+ "publish:desktop": "./scripts/publish-desktop-update.sh",
+ "smoke:desktop": "./scripts/smoke-test-desktop.sh",
"dev:app": "npm run dev -w @goldenchart/app",
"build:app": "npm run build -w @goldenchart/app",
"cap:sync": "npm run cap:sync -w @goldenchart/app",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 6c0a9fd..a1e2dbe 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -8,7 +8,8 @@
"exports": {
".": "./src/index.ts",
"./api": "./src/api/backendApi.ts",
- "./storage": "./src/storage/index.ts"
+ "./storage": "./src/storage/index.ts",
+ "./platform": "./src/platform.ts"
},
"dependencies": {
"@capacitor/preferences": "^7.0.0"
diff --git a/packages/shared/src/api/backendApi.ts b/packages/shared/src/api/backendApi.ts
index 1aec226..842ab0f 100644
--- a/packages/shared/src/api/backendApi.ts
+++ b/packages/shared/src/api/backendApi.ts
@@ -6,13 +6,17 @@
*/
import { storageGetSync, storageSetSync } from '../storage/index.js';
+import { getRuntimePlatform } from '../platform.js';
/** Flow layout stored with strategy (matches frontend StrategyFlowLayoutStore). */
export type StrategyFlowLayoutStore = Record;
-/** 배포 APK·exdev 기본 (HTTPS 443 미개방 — 반드시 http) */
+/** 배포 APK·exdev (모바일 APK — http) */
export const PRODUCTION_API_BASE = 'http://exdev.co.kr/api';
+/** Tauri desktop — macOS ATS 호환 (https 필수) */
+export const DESKTOP_API_BASE = 'https://exdev.co.kr/api';
+
function viteEnv(): Record | undefined {
return typeof import.meta !== 'undefined'
? (import.meta as ImportMeta & { env?: Record }).env
@@ -30,13 +34,29 @@ export function normalizeApiBaseUrl(url: string | null | undefined): string | nu
return u;
}
+/** desktop 빌드 — vite define __DESKTOP_CLIENT__ (localhost API 사용 금지) */
+declare const __DESKTOP_CLIENT__: boolean | undefined;
+
+function isDesktopClient(): boolean {
+ if (typeof __DESKTOP_CLIENT__ !== 'undefined' && __DESKTOP_CLIENT__) return true;
+ return typeof window !== 'undefined' && getRuntimePlatform() === 'desktop';
+}
+
function resolveApiBase(): string {
+ // Desktop(Tauri): https exdev (macOS ATS — http 차단)
+ if (isDesktopClient()) {
+ return DESKTOP_API_BASE;
+ }
+
const env = viteEnv();
const fromStorage = normalizeApiBaseUrl(storageGetSync('gc_api_base_url'));
const fromEnv = normalizeApiBaseUrl(env?.VITE_API_BASE_URL);
if (fromStorage) return fromStorage;
if (fromEnv) return fromEnv;
- if (env?.PROD) return PRODUCTION_API_BASE;
+ const platform = typeof window !== 'undefined' ? getRuntimePlatform() : 'web';
+ if (platform === 'mobile' || env?.PROD) {
+ return PRODUCTION_API_BASE;
+ }
return 'http://localhost:8080/api';
}
@@ -109,7 +129,7 @@ const DEFAULT_FETCH_TIMEOUT_MS = 45_000;
const LOGIN_FETCH_TIMEOUT_MS = 180_000;
function wrapNetworkError(e: unknown): Error {
- if (e instanceof TypeError || (e instanceof Error && /failed to fetch/i.test(e.message))) {
+ if (e instanceof TypeError || (e instanceof Error && /failed to fetch|load failed/i.test(e.message))) {
return new Error(
`서버에 연결할 수 없습니다 (${API_BASE}). Wi‑Fi·API 주소·앱 재설치 후 설정의 API URL을 확인하세요.`,
);
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 6a09674..33b0385 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -1,4 +1,5 @@
export * from './api/backendApi';
+export * from './platform';
export {
initStorage,
storageGet,
diff --git a/packages/shared/src/platform.ts b/packages/shared/src/platform.ts
new file mode 100644
index 0000000..900acec
--- /dev/null
+++ b/packages/shared/src/platform.ts
@@ -0,0 +1,27 @@
+/** 런타임 플랫폼 — web · desktop(Tauri) · mobile(Capacitor) */
+export type RuntimePlatform = 'web' | 'desktop' | 'mobile';
+
+export function isDesktop(): boolean {
+ return typeof window !== 'undefined' && '__TAURI__' in window;
+}
+
+export function isMobileCapacitor(): boolean {
+ if (typeof window === 'undefined') return false;
+ try {
+ // Capacitor injects on native; avoid hard dependency
+ const cap = (window as Window & { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor;
+ return cap?.isNativePlatform?.() === true;
+ } catch {
+ return false;
+ }
+}
+
+export function getRuntimePlatform(): RuntimePlatform {
+ if (isDesktop()) return 'desktop';
+ if (isMobileCapacitor()) return 'mobile';
+ return 'web';
+}
+
+export function isWeb(): boolean {
+ return getRuntimePlatform() === 'web';
+}
diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh
new file mode 100755
index 0000000..3354549
--- /dev/null
+++ b/scripts/build-desktop.sh
@@ -0,0 +1,128 @@
+#!/usr/bin/env bash
+# GoldenChart Desktop — macOS Jenkins 빌드 (macOS dmg + Windows NSIS cross-build)
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT"
+
+# shellcheck disable=SC1091
+source "$ROOT/scripts/desktop-dev-path.sh"
+# shellcheck disable=SC1091
+[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
+
+BUILD_MAC=1
+BUILD_WIN=1
+BUMP_VERSION="${BUMP_VERSION:-}"
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --mac) BUILD_WIN=0; shift ;;
+ --win) BUILD_MAC=0; shift ;;
+ --all) BUILD_MAC=1; BUILD_WIN=1; shift ;;
+ --bump-version) BUMP_VERSION="$2"; shift 2 ;;
+ *) echo "Unknown arg: $1"; exit 1 ;;
+ esac
+done
+
+echo "=== GoldenChart Desktop Build ==="
+echo "Root: $ROOT"
+echo "Git: $(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo 'n/a')"
+
+need_cmd() {
+ if ! command -v "$1" >/dev/null 2>&1; then
+ echo "Missing: $1 — run ./scripts/install-desktop-build-deps.sh"
+ exit 1
+ fi
+}
+
+need_cmd node
+need_cmd npm
+need_cmd cargo
+need_cmd rustc
+
+if [[ "$BUILD_WIN" -eq 1 ]]; then
+ need_cmd cargo-xwin
+ rustup target add x86_64-pc-windows-msvc 2>/dev/null || true
+fi
+
+CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
+if [[ -n "$BUMP_VERSION" ]]; then
+ node -e "
+const fs=require('fs'); const p=process.argv[1]; const b=process.argv[2];
+const c=JSON.parse(fs.readFileSync(p,'utf8'));
+const [a,x,y]=c.version.split('.').map(Number);
+let nv=c.version;
+if(b==='patch') nv=[a,x,y+1].join('.');
+else if(b==='minor') nv=[a,x+1,0].join('.');
+else if(b==='major') nv=[a+1,0,0].join('.');
+else nv=b;
+c.version=nv; fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
+console.log('version', nv);
+" "$CONF" "$BUMP_VERSION"
+fi
+
+VERSION="$(node -p "require('$CONF').version")"
+echo "Version: $VERSION"
+
+if [[ -f "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
+ chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
+fi
+chmod +x "$ROOT/scripts/prepare-tauri-build.sh" 2>/dev/null || true
+# shellcheck disable=SC1091
+source "$ROOT/scripts/prepare-tauri-build.sh"
+
+npm install
+
+echo "--- Vite build (desktop) ---"
+npm run build -w @goldenchart/desktop
+
+DESKTOP_DIR="$ROOT/desktop"
+TAURI_TARGET="$DESKTOP_DIR/src-tauri/target"
+ARTIFACT_DIR="$ROOT/dist-desktop"
+UPDATES_DIR="$ARTIFACT_DIR/updates"
+rm -rf "$ARTIFACT_DIR"
+mkdir -p "$ARTIFACT_DIR" "$UPDATES_DIR"
+
+collect_updater_artifacts() {
+ find "$TAURI_TARGET" \( \
+ -name '*.app.tar.gz' -o \
+ -name '*.tar.gz' -path '*/bundle/macos/*' -o \
+ -name '*.nsis.zip' -o \
+ -name '*-setup.nsis.zip' \
+ \) -type f 2>/dev/null | while read -r f; do
+ cp -f "$f" "$UPDATES_DIR/"
+ echo " updater bundle: $(basename "$f")"
+ done
+}
+
+if [[ "$BUILD_MAC" -eq 1 ]]; then
+ echo "--- Tauri build (macOS) ---"
+ (cd "$DESKTOP_DIR" && npm run tauri:build:mac)
+ find "$TAURI_TARGET/release/bundle" -name '*.dmg' -exec cp -f {} "$ARTIFACT_DIR/" \; 2>/dev/null || true
+ collect_updater_artifacts
+fi
+
+if [[ "$BUILD_WIN" -eq 1 ]]; then
+ echo "--- Tauri build (Windows NSIS cross) ---"
+ (cd "$DESKTOP_DIR" && npm run tauri:build:win)
+ find "$TAURI_TARGET" -path '*x86_64-pc-windows-msvc*' -name '*-setup.exe' -exec cp -f {} "$ARTIFACT_DIR/" \; 2>/dev/null || true
+ collect_updater_artifacts
+fi
+
+GIT_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo '')"
+GIT_SHORT="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo '')"
+cat > "$ARTIFACT_DIR/build-info.json" </dev/null || true
+echo "Done."
diff --git a/scripts/desktop-dev-path.sh b/scripts/desktop-dev-path.sh
new file mode 100644
index 0000000..6e66fff
--- /dev/null
+++ b/scripts/desktop-dev-path.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# GoldenChart desktop 빌드용 PATH — source scripts/desktop-dev-path.sh
+prepend_path() {
+ case ":${PATH}:" in
+ *":$1:"*) ;;
+ *) export PATH="$1:$PATH" ;;
+ esac
+}
+
+prepend_path "$HOME/.cargo/bin"
+[[ -x "$HOME/homebrew/bin/brew" ]] && eval "$("$HOME/homebrew/bin/brew" shellenv)"
+[[ -x /opt/homebrew/bin/brew ]] && eval "$(/opt/homebrew/bin/brew shellenv)"
+for llvm in /opt/homebrew/opt/llvm/bin "$HOME/homebrew/opt/llvm/bin"; do
+ [[ -d "$llvm" ]] && prepend_path "$llvm"
+done
diff --git a/scripts/generate-desktop-latest-json.mjs b/scripts/generate-desktop-latest-json.mjs
new file mode 100644
index 0000000..fa91008
--- /dev/null
+++ b/scripts/generate-desktop-latest-json.mjs
@@ -0,0 +1,83 @@
+#!/usr/bin/env node
+/**
+ * dist-desktop/updates → latest.json (Tauri updater v2)
+ *
+ * Usage:
+ * node scripts/generate-desktop-latest-json.mjs \
+ * --version 0.1.0 \
+ * --dir dist-desktop/updates \
+ * --base-url http://exdev.co.kr/desktop/updates \
+ * --notes "Release notes"
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+
+function arg(name, fallback) {
+ const i = process.argv.indexOf(name);
+ return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
+}
+
+const version = arg('--version', '0.1.0');
+const dir = path.resolve(arg('--dir', 'dist-desktop/updates'));
+const baseUrl = (arg('--base-url', 'http://exdev.co.kr/desktop/updates')).replace(/\/$/, '');
+const notes = arg('--notes', `GoldenChart Desktop ${version}`);
+
+if (!fs.existsSync(dir)) {
+ console.error('Directory not found:', dir);
+ process.exit(1);
+}
+
+const files = fs.readdirSync(dir);
+
+/** @type {Record} */
+const platforms = {};
+
+function addPlatform(platformKey, bundleName) {
+ const bundlePath = path.join(dir, bundleName);
+ if (!fs.existsSync(bundlePath)) return;
+ const sigPath = `${bundlePath}.sig`;
+ let signature = '';
+ if (fs.existsSync(sigPath)) {
+ signature = fs.readFileSync(sigPath, 'utf8').trim();
+ } else {
+ console.warn(`[warn] missing signature: ${sigPath}`);
+ return;
+ }
+ platforms[platformKey] = {
+ url: `${baseUrl}/${encodeURIComponent(bundleName)}`,
+ signature,
+ };
+}
+
+for (const f of files) {
+ if (f.endsWith('.sig')) continue;
+ const lower = f.toLowerCase();
+ if (lower.includes('aarch64') && (lower.endsWith('.tar.gz') || lower.endsWith('.app.tar.gz'))) {
+ addPlatform('darwin-aarch64', f);
+ } else if ((lower.includes('x64') || lower.includes('x86_64')) && lower.endsWith('.tar.gz')) {
+ addPlatform('darwin-x86_64', f);
+ } else if (lower.includes('aarch64') && lower.endsWith('.app.tar.gz')) {
+ addPlatform('darwin-aarch64', f);
+ } else if (lower.endsWith('.nsis.zip') || (lower.includes('setup') && lower.endsWith('.zip'))) {
+ addPlatform('windows-x86_64', f);
+ } else if (lower.endsWith('.app.tar.gz')) {
+ // universal mac fallback
+ if (!platforms['darwin-aarch64']) addPlatform('darwin-aarch64', f);
+ }
+}
+
+const manifest = {
+ version,
+ notes,
+ pub_date: new Date().toISOString(),
+ platforms,
+};
+
+const outPath = path.join(dir, 'latest.json');
+fs.writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`);
+console.log('Wrote', outPath);
+console.log('Platforms:', Object.keys(platforms).join(', ') || '(none — sign updater bundles first)');
+
+if (Object.keys(platforms).length === 0) {
+ process.exit(2);
+}
diff --git a/scripts/install-desktop-build-deps.sh b/scripts/install-desktop-build-deps.sh
new file mode 100755
index 0000000..83eba76
--- /dev/null
+++ b/scripts/install-desktop-build-deps.sh
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+# macOS Jenkins / 개발 Mac — GoldenChart Desktop 빌드 의존성 설치
+set -euo pipefail
+
+MAC_ONLY=0
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --mac-only) MAC_ONLY=1; shift ;;
+ *) echo "Unknown arg: $1 (supported: --mac-only)"; exit 1 ;;
+ esac
+done
+
+log() { echo "[install-desktop-deps] $*"; }
+
+if [[ "$(uname -s)" != "Darwin" ]]; then
+ log "이 스크립트는 macOS용입니다."
+ exit 1
+fi
+
+# Homebrew / Rust PATH (표준 prefix + 사용자 prefix)
+prepend_path() {
+ case ":${PATH}:" in
+ *":$1:"*) ;;
+ *) export PATH="$1:$PATH" ;;
+ esac
+}
+
+for prefix in /opt/homebrew "$HOME/homebrew" /usr/local; do
+ [[ -x "$prefix/bin/brew" ]] && prepend_path "$prefix/bin" && prepend_path "$prefix/sbin"
+ [[ -d "$prefix/opt/llvm/bin" ]] && prepend_path "$prefix/opt/llvm/bin"
+done
+prepend_path "$HOME/.cargo/bin"
+
+install_homebrew_user() {
+ local prefix="$HOME/homebrew"
+ if [[ -x "$prefix/bin/brew" ]]; then
+ return 0
+ fi
+ log "Homebrew 사용자 설치 ($prefix) — sudo 불필요"
+ mkdir -p "$prefix"
+ curl -fsSL https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C "$prefix"
+ prepend_path "$prefix/bin"
+ prepend_path "$prefix/sbin"
+}
+
+install_homebrew_system() {
+ if [[ -x /opt/homebrew/bin/brew ]] || [[ -x /usr/local/bin/brew ]]; then
+ return 0
+ fi
+ if [[ "${NONINTERACTIVE:-}" == "1" ]] && ! sudo -n true 2>/dev/null; then
+ log "sudo 없음 — $HOME/homebrew 로 설치합니다."
+ install_homebrew_user
+ return 0
+ fi
+ log "Homebrew 시스템 설치 (/opt/homebrew)..."
+ NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+}
+
+ensure_homebrew() {
+ if command -v brew >/dev/null 2>&1; then
+ return 0
+ fi
+ install_homebrew_system
+ if ! command -v brew >/dev/null 2>&1; then
+ install_homebrew_user
+ fi
+ if ! command -v brew >/dev/null 2>&1; then
+ log "Homebrew 설치 실패. https://brew.sh 수동 설치 후 재실행."
+ exit 1
+ fi
+ # shellcheck disable=SC1091
+ eval "$(brew shellenv)"
+}
+
+ensure_rust() {
+ if command -v rustc >/dev/null 2>&1; then
+ log "Rust 이미 설치됨: $(rustc --version)"
+ return 0
+ fi
+ log "Rust 설치 (rustup)..."
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
+ # shellcheck disable=SC1091
+ source "$HOME/.cargo/env"
+}
+
+setup_shell_profile() {
+ local marker="# GoldenChart desktop build PATH"
+ local profile="${ZDOTDIR:-$HOME}/.zprofile"
+ if [[ -f "$profile" ]] && grep -qF "$marker" "$profile" 2>/dev/null; then
+ log "shell profile 이미 설정됨: $profile"
+ return 0
+ fi
+ log "shell profile에 PATH 추가: $profile"
+ {
+ echo ""
+ echo "$marker"
+ echo 'export PATH="$HOME/.cargo/bin:$PATH"'
+ if [[ -x "$HOME/homebrew/bin/brew" ]]; then
+ echo 'eval "$("$HOME/homebrew/bin/brew" shellenv)"'
+ elif [[ -x /opt/homebrew/bin/brew ]]; then
+ echo 'eval "$(/opt/homebrew/bin/brew shellenv)"'
+ fi
+ echo 'export PATH="/opt/homebrew/opt/llvm/bin:$HOME/homebrew/opt/llvm/bin:$PATH"'
+ } >> "$profile"
+}
+
+ensure_homebrew
+setup_shell_profile
+
+if [[ "$MAC_ONLY" -eq 1 ]]; then
+ log "macOS 전용 모드 — NSIS/LLVM/cargo-xwin 생략"
+else
+ log "Homebrew 패키지 (NSIS, LLVM)..."
+ brew install nsis llvm 2>/dev/null || brew upgrade nsis llvm 2>/dev/null || true
+fi
+
+ensure_rust
+# shellcheck disable=SC1091
+[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
+
+if [[ "$MAC_ONLY" -eq 0 ]]; then
+ log "Windows 크로스 컴파일 타겟..."
+ rustup target add x86_64-pc-windows-msvc 2>/dev/null || true
+
+ if ! command -v cargo-xwin >/dev/null 2>&1; then
+ log "cargo-xwin 설치 (시간 소요)..."
+ cargo install cargo-xwin --locked
+ else
+ log "cargo-xwin 이미 설치됨"
+ fi
+fi
+
+log "완료. 검증:"
+node --version
+npm --version
+rustc --version
+cargo --version
+command -v makensis && makensis -VERSION || log "[INFO] makensis — Windows 빌드 시 필요"
+command -v cargo-xwin && cargo-xwin --version || log "[INFO] cargo-xwin — Windows 빌드 시 필요"
+log "새 터미널을 열거나: source ~/.zprofile"
diff --git a/scripts/jenkins-desktop-job.sh b/scripts/jenkins-desktop-job.sh
new file mode 100755
index 0000000..8a95b0f
--- /dev/null
+++ b/scripts/jenkins-desktop-job.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# @deprecated — scripts/jenkins-desktop-pipeline.sh + scripts/server-install-desktop-jenkins.sh 사용
+exec "$(dirname "$0")/jenkins-desktop-pipeline.sh" "$@"
diff --git a/scripts/jenkins-desktop-pipeline.sh b/scripts/jenkins-desktop-pipeline.sh
new file mode 100755
index 0000000..19ed48a
--- /dev/null
+++ b/scripts/jenkins-desktop-pipeline.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+# Jenkins goldenChart-Desktop-Pipeline — 전체 빌드·배포 진입점
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
+export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
+for prefix in "$HOME/homebrew" /opt/homebrew; do
+ [[ -x "$prefix/bin/brew" ]] && eval "$("$prefix/bin/brew" shellenv)"
+done
+# shellcheck disable=SC1091
+[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
+
+log() { echo "[desktop-pipeline $(date '+%H:%M:%S')] $*"; }
+
+log "=== goldenChart Desktop Pipeline #${BUILD_NUMBER:-local} ==="
+log "WORK_TREE=$WORK_TREE"
+
+cd "$WORK_TREE"
+
+# Git 동기화 (Jenkins SCM checkout 후에도 file:// bare 와 맞춤)
+if [[ -d "${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}" ]]; then
+ git --git-dir="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}" \
+ --work-tree="$WORK_TREE" checkout -f main 2>/dev/null || true
+fi
+
+chmod +x "$ROOT/scripts/"*.sh 2>/dev/null || true
+
+# 의존성 (최초 1회 — 이미 있으면 빠르게 통과)
+if ! command -v cargo-xwin >/dev/null 2>&1 || ! command -v rustc >/dev/null 2>&1; then
+ log "Desktop build deps 설치..."
+ "$ROOT/scripts/install-desktop-build-deps.sh"
+fi
+
+# updater pubkey (repo에 desktop/updater.pub 있으면 patch만)
+if [[ -f "$ROOT/desktop/updater.pub" ]]; then
+ "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
+elif [[ -f "${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" ]]; then
+ "$ROOT/scripts/setup-desktop-updater-keys.sh"
+fi
+
+BUILD_ARGS=(--all)
+if [[ -n "${DESKTOP_BUILD_MAC_ONLY:-}" ]]; then BUILD_ARGS=(--mac); fi
+if [[ -n "${DESKTOP_BUILD_WIN_ONLY:-}" ]]; then BUILD_ARGS=(--win); fi
+if [[ -n "${BUMP_VERSION:-}" ]]; then BUILD_ARGS+=(--bump-version "$BUMP_VERSION"); fi
+
+"$ROOT/scripts/build-desktop.sh" "${BUILD_ARGS[@]}"
+
+WORK_TREE="$WORK_TREE" \
+ARTIFACT_DIR="$ROOT/dist-desktop" \
+DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}" \
+TAURI_SIGNER_KEY="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" \
+DESKTOP_RELEASE_NOTES="${DESKTOP_RELEASE_NOTES:-Build ${BUILD_NUMBER:-local}}" \
+"$ROOT/scripts/publish-desktop-update.sh"
+
+# Jenkins artifact archive 경로
+if [[ -d "$ROOT/dist-desktop" ]]; then
+ log "Artifacts ready: $ROOT/dist-desktop"
+fi
+
+# 웹 frontend Docker 재배포 (latest.json static 포함) — 선택
+if [[ "${DESKTOP_DEPLOY_WEB:-0}" == "1" ]] && [[ -x "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}" ]]; then
+ log "Trigger web deploy (frontend only)..."
+ SERVICE_NAME=frontend "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}"
+fi
+
+log "Pipeline complete."
diff --git a/scripts/patch-tauri-updater-pubkey.sh b/scripts/patch-tauri-updater-pubkey.sh
new file mode 100755
index 0000000..26f9765
--- /dev/null
+++ b/scripts/patch-tauri-updater-pubkey.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
+CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
+
+if [[ ! -f "$PUB_PATH" ]]; then
+ echo "[patch-updater-pubkey] skip — no $PUB_PATH (run setup-desktop-updater-keys.sh)"
+ exit 0
+fi
+
+PUBKEY="$(grep -v '^[[:space:]]*#' "$PUB_PATH" | grep -v '^[[:space:]]*$' | tr -d '\n\r' | head -c 500)"
+
+if [[ -z "$PUBKEY" || "$PUBKEY" == "REPLACE_WITH_TAURI_SIGNER_PUBKEY" ]]; then
+ echo "[patch-updater-pubkey] skip — invalid or placeholder pubkey in $PUB_PATH"
+ exit 0
+fi
+
+CONF="$CONF" PUBKEY="$PUBKEY" node -e "
+const fs = require('fs');
+const confPath = process.env.CONF;
+const pubkey = process.env.PUBKEY;
+const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
+conf.plugins = conf.plugins || {};
+conf.plugins.updater = conf.plugins.updater || {};
+conf.plugins.updater.pubkey = pubkey;
+fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
+console.log('[patch-updater-pubkey] updated', confPath);
+"
diff --git a/scripts/prepare-tauri-build.sh b/scripts/prepare-tauri-build.sh
new file mode 100644
index 0000000..41b94e7
--- /dev/null
+++ b/scripts/prepare-tauri-build.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# tauri.conf.json — updater artifacts / pubkey (키 있을 때만 활성화)
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
+KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
+PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
+
+enable="false"
+if [[ -f "$KEY_PATH" && -f "$PUB_PATH" ]]; then
+ enable="true"
+ if [[ -x "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
+ "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
+ fi
+fi
+
+node -e "
+const fs = require('fs');
+const confPath = process.argv[1];
+const enable = process.argv[2] === 'true';
+const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
+conf.bundle = conf.bundle || {};
+conf.bundle.createUpdaterArtifacts = enable;
+conf.plugins = conf.plugins || {};
+conf.plugins.updater = conf.plugins.updater || {};
+if (!conf.plugins.updater.endpoints?.length) {
+ conf.plugins.updater.endpoints = ['https://exdev.co.kr/desktop/updates/latest.json'];
+}
+if (!conf.plugins.updater.pubkey) {
+ conf.plugins.updater.pubkey = 'REPLACE_WITH_TAURI_SIGNER_PUBKEY';
+}
+fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
+console.log('[prepare-tauri-build] createUpdaterArtifacts=' + enable);
+" "$CONF" "$enable"
+
+if [[ "$enable" == "true" ]]; then
+ export TAURI_SIGNING_PRIVATE_KEY="$(tr -d '\n\r' < "$KEY_PATH")"
+ echo "[prepare-tauri-build] updater signing enabled"
+else
+ unset TAURI_SIGNING_PRIVATE_KEY 2>/dev/null || true
+ echo "[prepare-tauri-build] updater signing skipped (no key)"
+fi
diff --git a/scripts/publish-desktop-update.sh b/scripts/publish-desktop-update.sh
new file mode 100755
index 0000000..0ff025f
--- /dev/null
+++ b/scripts/publish-desktop-update.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+# GoldenChart Desktop — updater 서명 + latest.json + 웹 static 배포
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT/dist-desktop}"
+UPDATES_SRC="${UPDATES_SRC:-$ARTIFACT_DIR/updates}"
+UPDATES_DIR="${UPDATES_DIR:-${WORK_TREE:-$ROOT}/frontend/public/desktop/updates}"
+WEB_STATIC_DIR="${WEB_STATIC_DIR:-$UPDATES_DIR}"
+BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}"
+KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
+DESKTOP_DIR="$ROOT/desktop"
+CONF="$DESKTOP_DIR/src-tauri/tauri.conf.json"
+VERSION="$(node -p "require('$CONF').version")"
+NOTES="${DESKTOP_RELEASE_NOTES:-GoldenChart Desktop $VERSION}"
+BUILD_INFO="$ARTIFACT_DIR/build-info.json"
+
+export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
+# shellcheck disable=SC1091
+[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
+
+log() { echo "[publish-desktop] $*"; }
+
+log "=== Publish Desktop v$VERSION ==="
+log "Updates src: $UPDATES_SRC"
+log "Web static: $WEB_STATIC_DIR"
+
+mkdir -p "$WEB_STATIC_DIR"
+
+# 설치 파일(dist-desktop/*.dmg, *setup.exe) 복사
+if [[ -d "$ARTIFACT_DIR" ]]; then
+ find "$ARTIFACT_DIR" -maxdepth 1 -type f \( -name '*.dmg' -o -name '*setup.exe' \) -exec cp -f {} "$WEB_STATIC_DIR/" \;
+fi
+
+PUBLISH_UPDATES="$WEB_STATIC_DIR"
+mkdir -p "$PUBLISH_UPDATES"
+
+if [[ -d "$UPDATES_SRC" ]]; then
+ cp -f "$UPDATES_SRC"/* "$PUBLISH_UPDATES/" 2>/dev/null || true
+fi
+
+# updater 번들 서명
+if [[ -f "$KEY_PATH" ]]; then
+ shopt -s nullglob
+ for bundle in "$PUBLISH_UPDATES"/*.tar.gz "$PUBLISH_UPDATES"/*.nsis.zip "$PUBLISH_UPDATES"/*.zip; do
+ [[ -f "$bundle" ]] || continue
+ [[ "$bundle" == *.sig ]] && continue
+ log "Signing $(basename "$bundle")"
+ (cd "$DESKTOP_DIR" && npx tauri signer sign -f "$KEY_PATH" "$bundle")
+ done
+ shopt -u nullglob
+else
+ log "[WARN] TAURI_SIGNER_KEY 없음 ($KEY_PATH) — updater 서명 생략"
+ log " ./scripts/setup-desktop-updater-keys.sh 실행 후 Jenkins Credential 등록"
+fi
+
+# build-info 복사
+if [[ -f "$BUILD_INFO" ]]; then
+ cp -f "$BUILD_INFO" "$PUBLISH_UPDATES/build-info.json"
+fi
+
+# latest.json
+set +e
+node "$ROOT/scripts/generate-desktop-latest-json.mjs" \
+ --version "$VERSION" \
+ --dir "$PUBLISH_UPDATES" \
+ --base-url "$BASE_URL" \
+ --notes "$NOTES"
+GEN_RC=$?
+set -e
+
+if [[ "$GEN_RC" -eq 2 ]]; then
+ log "[WARN] signed updater bundle 없음 — placeholder latest.json 작성"
+ cat > "$PUBLISH_UPDATES/latest.json" < "${JENKINS_HOME}/jobs/${JENKINS_JOB}/config.xml" << JENKINS_XML
+
+
+ GoldenChart Desktop — Tauri macOS dmg + Windows NSIS + updater
+ false
+
+
+ 2
+
+
+ file://${GIT_DIR}
+
+
+
+
+ */main
+
+
+ false
+
+
+
+ true
+ false
+ false
+ false
+
+
+ H/30 * * * *
+
+
+ false
+
+
+ #!/bin/bash
+set -euo pipefail
+export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:\$PATH"
+export WORK_TREE="${WORK_TREE}"
+export GIT_DIR="${GIT_DIR}"
+export BUILD_NUMBER="\${BUILD_NUMBER}"
+export TAURI_SIGNER_KEY="\${TAURI_SIGNER_KEY:-\$HOME/.tauri/goldenchart.key}"
+export DESKTOP_DEPLOY_WEB="\${DESKTOP_DEPLOY_WEB:-1}"
+"${REPO_ROOT}/scripts/jenkins-desktop-pipeline.sh"
+
+
+
+
+
+ dist-desktop/**
+ true
+ false
+ false
+ true
+
+
+
+
+JENKINS_XML
+
+if curl -sf -o /dev/null "http://127.0.0.1:8090/login" 2>/dev/null; then
+ curl -sf -X POST "http://127.0.0.1:8090/reload" 2>/dev/null || true
+fi
+
+log "완료: http://127.0.0.1:8090/job/${JENKINS_JOB}/"
+log ""
+log "Jenkins Credentials (Secret file):"
+log " ID: tauri-signer-key → \$HOME/.tauri/goldenchart.key"
+log ""
+log "최초 1회 (서버):"
+log " ./scripts/install-desktop-build-deps.sh"
+log " ./scripts/setup-desktop-updater-keys.sh"
+log " git add desktop/updater.pub && git commit"
diff --git a/scripts/setup-desktop-updater-keys.sh b/scripts/setup-desktop-updater-keys.sh
new file mode 100755
index 0000000..acc7246
--- /dev/null
+++ b/scripts/setup-desktop-updater-keys.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# Tauri updater minisign 키쌍 생성 — 1회 (Jenkins secret 또는 서버 로컬)
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
+PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
+DESKTOP_DIR="$ROOT/desktop"
+
+export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
+# shellcheck disable=SC1091
+[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
+
+mkdir -p "$(dirname "$KEY_PATH")"
+
+if [[ -f "$KEY_PATH" ]]; then
+ echo "기존 private key: $KEY_PATH"
+else
+ echo "새 updater 키 생성: $KEY_PATH"
+ GEN_OUT="$(cd "$DESKTOP_DIR" && npx tauri signer generate -w "$KEY_PATH" -f 2>&1)" || true
+ echo "$GEN_OUT"
+ if [[ ! -f "$KEY_PATH" ]]; then
+ echo "키 생성 실패. Rust/tauri-cli 확인 후 재시도."
+ exit 1
+ fi
+fi
+
+if [[ ! -f "$PUB_PATH" ]]; then
+ echo "Public key 추출 시도..."
+ if echo "$GEN_OUT" | grep -qi 'public key'; then
+ echo "$GEN_OUT" | grep -i 'public key' | tail -1 | sed -E 's/^[^:]*:[[:space:]]*//' > "$PUB_PATH"
+ fi
+ if [[ ! -s "$PUB_PATH" ]] && [[ -f "${KEY_PATH}.pub" ]]; then
+ cp "${KEY_PATH}.pub" "$PUB_PATH"
+ fi
+ if [[ ! -s "$PUB_PATH" ]]; then
+ echo "desktop/updater.pub 를 수동 생성하세요 (tauri signer generate 출력의 public key)."
+ exit 1
+ fi
+fi
+
+chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
+"$ROOT/scripts/patch-tauri-updater-pubkey.sh"
+
+echo ""
+echo "=== updater 키 설정 완료 ==="
+echo " Private: $KEY_PATH (git/Jenkins secret — 커밋 금지)"
+echo " Public: $PUB_PATH"
+echo "Jenkins: TAURI_SIGNER_KEY=$KEY_PATH"
diff --git a/scripts/smoke-test-desktop.sh b/scripts/smoke-test-desktop.sh
new file mode 100755
index 0000000..6afe300
--- /dev/null
+++ b/scripts/smoke-test-desktop.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# GoldenChart Desktop — smoke test (CI·로컬)
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}"
+FAIL=0
+
+ok() { echo "[OK] $*"; }
+fail() { echo "[FAIL] $*"; FAIL=1; }
+
+echo "=== Desktop Smoke Test ==="
+
+# 1) Vite 번들
+if npm run build -w @goldenchart/desktop >/dev/null 2>&1; then
+ ok "desktop vite build"
+else
+ fail "desktop vite build"
+fi
+
+# 2) updater pubkey
+if [[ -f "$ROOT/desktop/updater.pub" ]]; then
+ ok "desktop/updater.pub exists"
+else
+ echo "[WARN] desktop/updater.pub 없음 — setup-desktop-updater-keys.sh 필요"
+fi
+
+# 3) tauri.conf pubkey not placeholder
+PUB="$(node -p "require('$ROOT/desktop/src-tauri/tauri.conf.json').plugins?.updater?.pubkey || ''")"
+if [[ -n "$PUB" && "$PUB" != "REPLACE_WITH_TAURI_SIGNER_PUBKEY" ]]; then
+ ok "tauri.conf updater pubkey configured"
+else
+ echo "[WARN] tauri.conf pubkey 미설정"
+fi
+
+# 4) latest.json (local or remote)
+LOCAL_LATEST="$ROOT/frontend/public/desktop/updates/latest.json"
+if [[ -f "$LOCAL_LATEST" ]]; then
+ ok "local latest.json"
+ node -e "JSON.parse(require('fs').readFileSync('$LOCAL_LATEST','utf8'))" && ok "latest.json valid JSON"
+fi
+
+if curl -sf "${BASE_URL}/latest.json" >/dev/null 2>&1; then
+ ok "remote latest.json ${BASE_URL}/latest.json"
+else
+ echo "[WARN] remote latest.json unreachable (배포 전 정상)"
+fi
+
+# 5) 필수 스크립트
+for s in build-desktop.sh publish-desktop-update.sh jenkins-desktop-pipeline.sh; do
+ if [[ -x "$ROOT/scripts/$s" ]]; then ok "scripts/$s"; else fail "scripts/$s missing"; fi
+done
+
+if [[ "$FAIL" -eq 0 ]]; then
+ echo "=== Smoke test passed ==="
+ exit 0
+fi
+echo "=== Smoke test failed ==="
+exit 1