Compare commits

...
Author SHA1 Message Date
renovate[bot] 044335a324 chore(deps): update cargo dependencies 2026-10-28 02:56:26 +00:00
Shubham Padkonde 3953e125b2 fix(profile): ignore null entries in the rules editor (#8432) 2026-10-27 17:39:05 +00:00
nrenvo cbd5ed73a5 fix(theme): allow injected CSS to style Monaco view containers (#8385)
`@scope`'s `to` bound excludes the boundary elements themselves, so rules
targeting `.view-lines` never matched. Move the bound to its children
with `> *`: the container becomes styleable, its internals stay excluded.
2026-10-27 15:13:58 +00:00
Tunglies 02e5e0e4a6 ci: gate frontend and rust checks by changed paths (#8433)
The paths-filter output in lint-clippy.yml only gated two echo steps;
checkout, toolchain setup and clippy itself ran unconditionally, so
frontend-only PRs still burned a 3-runner matrix on a full clippy build
plus the logging-check tool install.

- lint-clippy/rustfmt/frontend-check: move path filters to workflow
  level so irrelevant PRs spawn no runner at all
- split contract-parity into its own workflow; its trigger set spans
  both sides and workflow paths cannot vary per job
- clippy paths now cover crates/** and workspace Cargo files, which the
  old src-tauri/** filter missed

dev has no required status checks, so path-filtered workflows cannot
get stuck in "Expected".
2026-10-27 16:36:49 +08:00
0498ba3e4f fix(proxies): render configured groups in global mode (#8402)
* fix(proxies): render configured groups in global mode

Global mode previously only rendered the synthetic GLOBAL group and
sliced off its header, which flattened every user-defined group into
a non-expandable proxy row. Configured groups could no longer be
opened or inspected. Render the same group list as rule/script mode
so every group keeps its own header and expand toggle.

Fixes #8376

* style: keep useRule assignment on one line

* fix & changelog

* fix: hover when global proxy

---------

Co-authored-by: susu-Sugar <214122492+susu-Sugar@users.noreply.github.com>
Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2026-10-27 01:41:19 +00:00
Tunglies f39644ee6b chore(typecheck): replace tsc with tsrs as the typecheck gate (#8428)
tsrs (Rust port of the TypeScript 7 checker) replaces tsc --noEmit in
the typecheck script, web:build, and CI. Measured on this repository:
~0.4s vs ~3.5s wall time and ~280 MB vs ~750 MB peak RSS, with
identical zero-diagnostic results on the current tree. The typescript
package stays installed: typescript-eslint projectService and
scripts/cleanup-unused-i18n.mjs consume its compiler API.
2026-10-27 01:34:29 +00:00
488f022c05 feat(media-unlock): add Muse AI (muse.ai) detection (#8411)
* feat(media-unlock): add Muse AI detection

muse.ai redirects every region to Meta's aymh anti-bot gateway before the app loads, and a plain HTTP client cannot finish the JavaScript challenge. Report the result as a new Risky status (exit reachable, availability unverifiable) together with the exit region instead of a misleading Yes/No.

Signed-off-by: ❤是纱雾酱哟~ <49941141+Dragon1573@users.noreply.github.com>

* style(media-unlock): apply rustfmt and biome formatting

Signed-off-by: ❤是纱雾酱哟~ <49941141+Dragon1573@users.noreply.github.com>

* docs(changelog): Update changelog

Signed-off-by: ❤是纱雾酱哟~ <49941141+Dragon1573@users.noreply.github.com>

---------

Signed-off-by: ❤是纱雾酱哟~ <49941141+Dragon1573@users.noreply.github.com>
Co-authored-by: susu-Sugar <214122492+susu-Sugar@users.noreply.github.com>
Co-authored-by: ❤是纱雾酱哟~ <49941141+Dragon1573@users.noreply.github.com>
2026-10-26 17:17:09 +00:00
Ekko82 eefd025806 feat(home): add an IP auto-refresh toggle (#8403) 2026-10-26 02:04:55 +00:00
36 changed files with 346 additions and 179 deletions
+80
View File
@@ -0,0 +1,80 @@
name: Contract Parity
on:
pull_request:
paths:
- "src-tauri/**/*.rs"
- "crates/**/*.rs"
- "Cargo.toml"
- "Cargo.lock"
- "src-tauri/Cargo.toml"
- "crates/**/Cargo.toml"
- "rust-toolchain.toml"
- "src-tauri/tauri.conf*.json"
- "src/services/bindings.ts"
- "scripts/gen-contract.mjs"
- "scripts/check-contract-changes.mjs"
- "biome.json"
- "biome.jsonc"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "scripts/contract-inputs.json"
- ".github/workflows/contract-parity.yml"
- ".husky/pre-push"
workflow_dispatch:
permissions:
contents: read
env:
HUSKY: 0
jobs:
contract-parity:
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v7
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: 'v1-rust'
key: 'rust-shared-stable-ubuntu-22.04-x86_64-unknown-linux-gnu-contract'
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install pnpm
uses: pnpm/action-setup@v6
with:
run_install: false
- uses: actions/setup-node@v7
with:
node-version: '24.22.1'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Provision sidecar placeholders for tauri-build
run: |
mkdir -p src-tauri/sidecar src-tauri/resources dist
touch dist/index.html
for bin in clash-verge-service clash-verge-service-install clash-verge-service-uninstall verge-mihomo verge-mihomo-alpha; do
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
done
- name: Check generated contract without changing the checkout
run: pnpm gen:contract --check
+13 -103
View File
@@ -2,11 +2,23 @@ name: Frontend Check
on:
pull_request:
paths:
- "src/**"
- "**/*.js"
- "**/*.ts"
- "**/*.tsx"
- "**/*.css"
- "**/*.scss"
- "**/*.json"
- "**/*.md"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "vite.config.*"
- ".github/workflows/frontend-check.yml"
workflow_dispatch:
permissions:
contents: read
pull-requests: read
env:
HUSKY: 0
@@ -17,46 +29,17 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Check frontend changes
id: check_frontend
uses: dorny/paths-filter@v4
with:
filters: |
frontend:
- 'src/**'
- '**/*.js'
- '**/*.ts'
- '**/*.tsx'
- '**/*.css'
- '**/*.scss'
- '**/*.json'
- '**/*.md'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'knip.json'
- 'eslint.config.ts'
- 'tsconfig.json'
- 'vite.config.*'
- name: Skip if no frontend changes
if: steps.check_frontend.outputs.frontend != 'true'
run: echo "No frontend changes, skipping frontend checks."
- name: Install pnpm
if: steps.check_frontend.outputs.frontend == 'true'
uses: pnpm/action-setup@v6
with:
run_install: false
- uses: actions/setup-node@v7
if: steps.check_frontend.outputs.frontend == 'true'
with:
node-version: '24.22.1'
cache: 'pnpm'
- name: Restore pnpm cache
if: steps.check_frontend.outputs.frontend == 'true'
uses: actions/cache@v6
with:
path: ~/.pnpm-store
@@ -65,94 +48,21 @@ jobs:
pnpm-shared-stable-${{ runner.os }}-
- run: pnpm install --frozen-lockfile
if: steps.check_frontend.outputs.frontend == 'true'
- name: Run Prettier check
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm format:check
- name: Run ESLint
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm lint
- name: Run TypeScript typecheck
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm typecheck
- name: Run tests
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm test
- name: Run Knip
if: steps.check_frontend.outputs.frontend == 'true'
run: pnpm knip:check
- name: Architecture gates (listen/emit only in the event bus, invoke only in services)
if: steps.check_frontend.outputs.frontend == 'true'
run: node scripts/check-architecture.mjs listen emit invoke
contract-parity:
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@v7
- name: Check contract inputs
id: check_inputs
uses: dorny/paths-filter@v4
with:
filters: scripts/contract-inputs.json
- name: Skip if contract inputs unchanged
if: steps.check_inputs.outputs.contract != 'true'
run: echo "No contract inputs changed, skipping parity check."
- name: Install Rust Stable
if: steps.check_inputs.outputs.contract == 'true'
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Rust Cache
if: steps.check_inputs.outputs.contract == 'true'
uses: Swatinem/rust-cache@v2
with:
prefix-key: 'v1-rust'
key: 'rust-shared-stable-ubuntu-22.04-x86_64-unknown-linux-gnu-contract'
workspaces: |
. -> target
cache-all-crates: true
cache-workspace-crates: true
- name: Install dependencies
if: steps.check_inputs.outputs.contract == 'true'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install pnpm
if: steps.check_inputs.outputs.contract == 'true'
uses: pnpm/action-setup@v6
with:
run_install: false
- uses: actions/setup-node@v7
if: steps.check_inputs.outputs.contract == 'true'
with:
node-version: '24.22.1'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
if: steps.check_inputs.outputs.contract == 'true'
- name: Provision sidecar placeholders for tauri-build
if: steps.check_inputs.outputs.contract == 'true'
run: |
mkdir -p src-tauri/sidecar src-tauri/resources dist
touch dist/index.html
for bin in clash-verge-service clash-verge-service-install clash-verge-service-uninstall verge-mihomo verge-mihomo-alpha; do
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
done
- name: Check generated contract without changing the checkout
if: steps.check_inputs.outputs.contract == 'true'
run: pnpm gen:contract --check
+8 -23
View File
@@ -2,6 +2,13 @@ name: Clippy Lint
on:
pull_request:
paths:
- "src-tauri/**"
- "crates/**"
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain.toml"
- ".github/workflows/lint-clippy.yml"
workflow_dispatch:
env:
HUSKY: 0
@@ -23,28 +30,6 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Check src-tauri changes
if: github.event_name != 'workflow_dispatch'
id: check_changes
uses: dorny/paths-filter@v4
with:
filters: |
rust:
- 'src-tauri/**'
- name: Skip if src-tauri not changed
if: github.event_name != 'workflow_dispatch' && steps.check_changes.outputs.rust != 'true'
run: echo "No src-tauri changes, skipping clippy lint."
- name: Continue if src-tauri changed
if: github.event_name != 'workflow_dispatch' && steps.check_changes.outputs.rust == 'true'
run: echo "src-tauri changed, running clippy lint."
- name: Manual trigger - always run
if: github.event_name == 'workflow_dispatch'
run: |
echo "Manual trigger detected: skipping changes check and running clippy."
- name: Checkout Repository
uses: actions/checkout@v7
@@ -79,7 +64,7 @@ jobs:
run: cargo clippy-all
- name: Run Windows security tests
if: matrix.os == 'windows-latest' && (github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.rust == 'true')
if: matrix.os == 'windows-latest'
working-directory: ./src-tauri
run: cargo test --all-features --target ${{ matrix.target }}
+4 -15
View File
@@ -6,6 +6,10 @@ name: Check Formatting
on:
pull_request:
paths:
- "src-tauri/**"
- "**/*.rs"
- ".github/workflows/rustfmt.yml"
permissions:
contents: read
@@ -19,25 +23,10 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Check Rust changes
id: check_rust
uses: dorny/paths-filter@v4
with:
filters: |
rust:
- 'src-tauri/**'
- '**/*.rs'
- name: Skip if no Rust changes
if: steps.check_rust.outputs.rust != 'true'
run: echo "No Rust changes, skipping rustfmt."
- name: install Rust stable and rustfmt
if: steps.check_rust.outputs.rust == 'true'
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: run cargo fmt
if: steps.check_rust.outputs.rust == 'true'
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
+2 -2
View File
@@ -63,11 +63,11 @@ tracing = "0.1.44"
tauri = { version = "2.12.4" }
tauri-plugin-clipboard-manager = "2.5.0"
parking_lot = { version = "0.12.5", features = ["hardware-lock-elision"] }
parking_lot = { version = "0.13.0", features = ["hardware-lock-elision"] }
anyhow = "1.0.104"
chrono = "0.4.45"
criterion = { version = "0.8.2", features = ["async_tokio"] }
tokio = { version = "1.53.3", features = [
tokio = { version = "1.54.1", features = [
"rt-multi-thread",
"macros",
"time",
+5
View File
@@ -3,10 +3,13 @@
<details>
<summary><strong> 🐞 修复问题 </strong></summary>
- 修复规则扩展包含空列表项时,「编辑规则」界面报错的问题
- 修复托盘测速与代理页行为不一致的问题,移除托盘「测试延迟」入口
- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置
- 修复 首页 网站测速失败后卡片一直显示测试中
- 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新
- 修复 代理 在全局模式下无法展开节点组
- 修复 自定义 CSS 无法修改编辑器字体和字号的问题
**🐧 Linux**
@@ -18,6 +21,8 @@
<summary><strong> ✨ 新增功能 </strong></summary>
- 新增虚拟网卡设置的「DNS 模式」选项,支持配置 mihomo 系统 DNS 行为
- 新增「IP 信息」自动刷新开关,关闭后仍可手动刷新
- 新增 Muse AI 解锁检测,针对 Muse AI 特有的 Cloudflare 人机验证增加「Risky」状态标记
</details>
+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
specta = { version = "=2.0.0-rc.27", features = ["derive", "function"] }
clash-verge-logging = { workspace = true }
chrono = { workspace = true }
log = { workspace = true }
@@ -11,6 +11,7 @@ mod chatgpt;
mod claude;
mod disney_plus;
mod gemini;
mod muse;
mod netflix;
mod prime_video;
mod spotify;
@@ -30,6 +31,7 @@ enum UnlockCheck {
ChatGptWeb,
Claude,
Gemini,
MuseAi,
YouTubePremium,
BahamutAnime,
Netflix,
@@ -46,6 +48,7 @@ impl UnlockCheck {
Self::ChatGptWeb,
Self::Claude,
Self::Gemini,
Self::MuseAi,
Self::YouTubePremium,
Self::BahamutAnime,
Self::Netflix,
@@ -66,6 +69,7 @@ impl UnlockCheck {
Self::ChatGptWeb => chatgpt::CHATGPT_WEB_NAME,
Self::Claude => claude::CLAUDE_NAME,
Self::Gemini => gemini::GEMINI_NAME,
Self::MuseAi => muse::MUSE_AI_NAME,
Self::YouTubePremium => youtube::YOUTUBE_PREMIUM_NAME,
Self::BahamutAnime => bahamut::BAHAMUT_ANIME_NAME,
Self::Netflix => netflix::NETFLIX_NAME,
@@ -83,6 +87,7 @@ impl UnlockCheck {
Self::ChatGptWeb => chatgpt::check_chatgpt(client).await,
Self::Claude => claude::check_claude(client).await,
Self::Gemini => gemini::check_gemini(client).await,
Self::MuseAi => muse::check_muse_ai(client).await,
Self::YouTubePremium => youtube::check_youtube_premium(client).await,
Self::BahamutAnime => bahamut::check_bahamut_anime(client).await,
Self::Netflix => netflix::check_netflix(client).await,
@@ -0,0 +1,48 @@
use reqwest::Client;
use crate::utils::{classify_restricted_status, get_trace_location};
use super::UnlockItem;
pub(crate) const MUSE_AI_NAME: &str = "Muse AI";
const MUSE_URL: &str = "https://muse.ai";
const CLOUDFLARE_TRACE_URL: &str = "https://www.cloudflare.com/cdn-cgi/trace";
// The aymh ("Are You a Human") gateway answers plain HTTP requests with a
// generic Meta error page that always contains the Facebook logo asset.
const AYMH_ERROR_MARKERS: &[&str] = &["Sorry, something went wrong", "static.facebook.com/images/logos"];
pub(super) async fn check_muse_ai(client: &Client) -> UnlockItem {
// muse.ai itself is served by Vercel, so resolve the exit region through
// the generic Cloudflare trace endpoint instead of a service-specific one.
let region = get_trace_location(client, CLOUDFLARE_TRACE_URL)
.await
.map(|code| code.trim().to_ascii_uppercase())
.map(|code| UnlockItem::region_label(&code));
let Ok(response) = client.get(MUSE_URL).send().await else {
return UnlockItem::checked(MUSE_AI_NAME, "Failed (Network Connection)", region);
};
let status = response.status();
let landed_on_aymh = response.url().host_str() == Some("auth.muse.ai");
let body = response.text().await.unwrap_or_default();
// Every region is redirected to the aymh anti-bot gateway before the app
// loads, and a non-browser HTTP client cannot finish the JavaScript
// challenge. It returns the Meta error page, so reaching this point only
// proves the exit can connect to Muse's auth infrastructure; availability
// cannot be verified over plain HTTP and is reported as `Risky`.
let behind_anti_bot = landed_on_aymh || AYMH_ERROR_MARKERS.iter().any(|marker| body.contains(marker));
let result = if behind_anti_bot {
"Risky"
} else if status.is_success() {
"Yes"
} else {
classify_restricted_status(status).unwrap_or("Failed")
};
UnlockItem::checked(MUSE_AI_NAME, result, region)
}
@@ -5,7 +5,7 @@ edition = "2024"
rust-version = "1.91"
[dependencies]
specta = { version = "=2.0.0-rc.25", features = ["derive", "function"] }
specta = { version = "=2.0.0-rc.27", features = ["derive", "function"] }
tauri = { workspace = true }
tauri-plugin-clipboard-manager = { workspace = true }
parking_lot = { workspace = true }
+3 -2
View File
@@ -14,7 +14,7 @@
"build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release",
"tauri": "tauri",
"web:dev": "vite",
"web:build": "tsc --noEmit && vite build",
"web:build": "tsrs -p . --noEmit && vite build",
"web:serve": "vite preview",
"prebuild": "node scripts/prebuild.mjs",
"updater": "node scripts/updater.mjs",
@@ -32,7 +32,7 @@
"i18n:types": "node scripts/generate-i18n-keys.mjs",
"gen:contract": "node scripts/gen-contract.mjs",
"arch:check": "node scripts/check-architecture.mjs listen emit invoke",
"typecheck": "tsc --noEmit",
"typecheck": "tsrs -p . --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"knip:check": "knip",
@@ -128,6 +128,7 @@
"sass": "^1.104.1",
"tar": "^7.5.22",
"terser": "^5.51.2",
"tsrs": "0.20.0-ts7.1.0-beta",
"typescript": "^6.0.3",
"typescript-eslint": "^8.70.0",
"vite": "^8.3.0",
+88 -3
View File
@@ -416,6 +416,9 @@ importers:
terser:
specifier: ^5.51.2
version: 5.51.2
tsrs:
specifier: 0.20.0-ts7.1.0-beta
version: 0.20.0-ts7.1.0-beta
typescript:
specifier: ^6.0.3
version: 6.0.3
@@ -2218,6 +2221,52 @@ packages:
'@tauri-apps/plugin-updater@2.14.0':
resolution: {integrity: sha512-gdylJ9tger4hkyOn24QZd6MNOKVo2Eh53iw5th2Qj1vKTR/+u4PIwJWuiis5hohYvobn6iOvlnALfrqIPSzh7Q==}
'@tsrs/darwin-arm64@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-mhHj8QGUmdJDBvJEnMXx5P/jvAKXYU3hy/z3qxrWBo8XHlbLES32PgC0ZpTRbJA960ReTpVqqvCOYaC6EL32/Q==}
engines: {node: '>=16.20.0'}
cpu: [arm64]
os: [darwin]
'@tsrs/darwin-x64@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-CsRUnyYRLrhmChS7ol7LgmdqMXTu+mVlDU+7JgbW8RpkJRGLZ/zgEDEOeD8qrndt8rpP9oW1T1SJ36HSzB3/sg==}
engines: {node: '>=16.20.0'}
cpu: [x64]
os: [darwin]
'@tsrs/linux-arm64-gnu@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-VNwpdI/iICwQMMlS/MoSZCvKFByEn2+g7D0HREWG+3r3GRayXgbLv8iaNSHKT3QGcu8pXr3lH9170ghJQi2zRg==}
engines: {node: '>=16.20.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tsrs/linux-arm64-musl@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-cXnqPpmj5gzeGi4u1OV0ctd5df5t4ivwyeToCa4pBrKtjYodu0EeAo5Y76SHbJwggbiA1gzEJVj1vvuciQm5mQ==}
engines: {node: '>=16.20.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tsrs/linux-x64-gnu@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-jlhJSrO9lxNh0TF5EU1i+Frok5sRWsBvCuQhmdqMZKIN2pSHsDZl3CSmKUsJ4tNr50np9USCPpkkxwby2Eiqhg==}
engines: {node: '>=16.20.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tsrs/linux-x64-musl@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-pestGEPplhEPzIERc7zwuQ5zhh9ZwMSvAPNQy5TM0je/QuaizmM1zKoPYJ4NiaPzLh2M25HtP4HcpumTqmF3KQ==}
engines: {node: '>=16.20.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tsrs/win32-x64@0.20.0-ts7.1.0-beta':
resolution: {integrity: sha512-sstxBfhjjOSYvSHFfSmROau2oGAVNLwAnj+H0h+ywLo7N7ueFKIDk14HAkIYa/F9iVboiICCpHDY/pmBO71sSg==}
engines: {node: '>=16.20.0'}
cpu: [x64]
os: [win32]
'@tybys/wasm-util@0.10.4':
resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==}
@@ -4770,6 +4819,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsrs@0.20.0-ts7.1.0-beta:
resolution: {integrity: sha512-8NifAn2tcna3t/nluu1yFvp0fYE28vElDpnD4jiWik69+MDjiHgxdjH8sUYUEFmf/2hl6QIE5ExVGpJzQbBDPw==}
engines: {node: '>=16.20.0'}
hasBin: true
tunnel@0.0.6:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
@@ -6882,6 +6936,27 @@ snapshots:
dependencies:
'@tauri-apps/api': 2.12.4
'@tsrs/darwin-arm64@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/darwin-x64@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/linux-arm64-gnu@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/linux-arm64-musl@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/linux-x64-gnu@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/linux-x64-musl@0.20.0-ts7.1.0-beta':
optional: true
'@tsrs/win32-x64@0.20.0-ts7.1.0-beta':
optional: true
'@tybys/wasm-util@0.10.4':
dependencies:
tslib: 2.8.1
@@ -7222,7 +7297,7 @@ snapshots:
'@wdio/globals@9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))':
dependencies:
expect-webdriverio: 6.1.0(@wdio/globals@9.31.3)(@wdio/logger@10.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
expect-webdriverio: 6.1.0(@wdio/globals@9.31.3)(@wdio/logger@9.29.1)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
webdriverio: 10.1.0(supports-color@7.2.0)(yauzl@2.10.0)
'@wdio/logger@10.1.0':
@@ -8206,11 +8281,11 @@ snapshots:
expect-type@1.4.0: {}
expect-webdriverio@6.1.0(@wdio/globals@9.31.3)(@wdio/logger@10.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0)):
expect-webdriverio@6.1.0(@wdio/globals@9.31.3)(@wdio/logger@9.29.1)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0)):
dependencies:
'@vitest/snapshot': 4.1.11
'@wdio/globals': 9.31.3(expect-webdriverio@6.1.0)(webdriverio@10.1.0(supports-color@7.2.0)(yauzl@2.10.0))
'@wdio/logger': 10.1.0
'@wdio/logger': 9.29.1
deep-eql: 5.0.2
expect: 30.5.2
jest-matcher-utils: 30.5.2
@@ -9865,6 +9940,16 @@ snapshots:
tslib@2.8.1: {}
tsrs@0.20.0-ts7.1.0-beta:
optionalDependencies:
'@tsrs/darwin-arm64': 0.20.0-ts7.1.0-beta
'@tsrs/darwin-x64': 0.20.0-ts7.1.0-beta
'@tsrs/linux-arm64-gnu': 0.20.0-ts7.1.0-beta
'@tsrs/linux-arm64-musl': 0.20.0-ts7.1.0-beta
'@tsrs/linux-x64-gnu': 0.20.0-ts7.1.0-beta
'@tsrs/linux-x64-musl': 0.20.0-ts7.1.0-beta
'@tsrs/win32-x64': 0.20.0-ts7.1.0-beta
tunnel@0.0.6: {}
type-check@0.4.0:
+8
View File
@@ -10,3 +10,11 @@ allowBuilds:
unrs-resolver: true
minimumReleaseAgeExclude:
- dayjs@1.11.21
- "@tsrs/darwin-arm64@0.20.0-ts7.1.0-beta"
- "@tsrs/darwin-x64@0.20.0-ts7.1.0-beta"
- "@tsrs/linux-arm64-gnu@0.20.0-ts7.1.0-beta"
- "@tsrs/linux-arm64-musl@0.20.0-ts7.1.0-beta"
- "@tsrs/linux-x64-gnu@0.20.0-ts7.1.0-beta"
- "@tsrs/linux-x64-musl@0.20.0-ts7.1.0-beta"
- "@tsrs/win32-x64@0.20.0-ts7.1.0-beta"
- tsrs@0.20.0-ts7.1.0-beta
+7
View File
@@ -79,6 +79,13 @@
"matchPackageNames": ["typescript"],
"allowedVersions": "<7.0.0 || >=7.1.0"
},
{
"description": "Review tsrs bumps alone; the version encodes the pinned TypeScript release",
"matchPackageNames": ["tsrs", "/^@tsrs//"],
"groupName": "tsrs",
"rangeStrategy": "pin",
"automerge": false
},
{
"description": "Prevent project from supply chain attack",
"matchDatasources": ["npm"],
+1 -1
View File
@@ -17,7 +17,7 @@
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"scripts/contract-inputs.json",
".github/workflows/frontend-check.yml",
".github/workflows/contract-parity.yml",
".husky/pre-push"
]
}
+7 -7
View File
@@ -58,8 +58,8 @@ smartstring = { workspace = true, features = ["serde"] }
bitflags = { workspace = true }
socket2 = { workspace = true }
warp = { version = "0.4.3", features = ["server"] }
open = "5.4.4"
rfd = { version = "0.16.0", default-features = false, features = ["common-controls-v6"] }
open = "5.4.5"
rfd = { version = "0.17.2", default-features = false, features = ["common-controls-v6"] }
dirs = "7.0.0"
dunce = "1.0.5"
nanoid = "0.5"
@@ -79,7 +79,7 @@ tauri-plugin-fs = "2.7.1"
tauri-plugin-process = "2.4.0"
tauri-plugin-deep-link = "2.6.2"
tauri-plugin-window-state = "2.5.0"
zip = "9.0.4"
zip = "9.1.3"
flate2 = "1.1.10"
reqwest_dav = "0.3.3"
aes-gcm = { version = "0.11.1" }
@@ -101,16 +101,16 @@ async-trait = "0.1.92"
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.6", version = "2.7.6", features = [
"client",
] }
arc-swap = "1.9.2"
arc-swap = "1.9.3"
tokio-rustls = "0.26.6"
rustls = { version = "0.23.46", features = ["ring"] }
webpki-roots = "1.0.9"
webpki-roots = "1.0.11"
dark-light = "3.0.0"
bytes = "1.12.1"
tokio-util = { version = "0.7.20", features = ["time"] }
specta = { version = "=2.0.0-rc.25", features = ["derive", "function", "serde_json"] }
specta = { version = "=2.0.0-rc.27", features = ["derive", "function", "serde_json"] }
tauri-specta = { version = "=2.0.0-rc.25", features = ["derive", "typescript"] }
specta-typescript = "=0.0.12"
specta-typescript = "=0.0.14"
[target.'cfg(unix)'.dependencies]
libc = "0.2.191"
+37 -9
View File
@@ -4,10 +4,19 @@ import {
VisibilityOffOutlined,
VisibilityOutlined,
} from '@mui/icons-material'
import { Box, Button, IconButton, Skeleton, Typography } from '@mui/material'
import {
Box,
Button,
FormControlLabel,
IconButton,
Skeleton,
Switch,
Typography,
} from '@mui/material'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useEffect } from 'foxact/use-abortable-effect'
import { useIntersection } from 'foxact/use-intersection'
import { useLocalStorage } from 'foxact/use-local-storage'
import type { XOR } from 'foxts/ts-xor'
import {
forwardRef,
@@ -99,9 +108,13 @@ const IPInfoCardContainer = forwardRef<HTMLElement, React.PropsWithChildren>(
export const IpInfoCard = () => {
const { t } = useTranslation()
const [showIp, setShowIp] = useState(false)
const [autoRefresh, setAutoRefresh] = useLocalStorage(
'ip-info-auto-refresh',
true,
)
const appWindow = useMemo(() => getCurrentWebviewWindow(), [])
// Once intersected, refreshes stay enabled until unmount.
// Once intersected, the card stays eligible for refresh until unmount.
const [containerRef, hasIntersected, _resetIntersected] = useIntersection({
rootMargin: '0px',
})
@@ -151,6 +164,8 @@ export const IpInfoCard = () => {
})
useEffect(() => {
if (!autoRefresh) return
let timer: number | null = null
// Prefer DOM intersection state; Tauri visibility is unreliable on some platforms.
@@ -190,7 +205,7 @@ export const IpInfoCard = () => {
if (timer != null) clearInterval(timer)
document.removeEventListener('visibilitychange', onVisibilityChange)
}
}, [hasIntersected])
}, [autoRefresh, hasIntersected])
const toggleShowIp = useCallback(() => {
setShowIp((prev) => !prev)
@@ -364,12 +379,25 @@ export const IpInfoCard = () => {
fontSize: '0.7rem',
}}
>
<Typography variant="caption">
{t('home.components.ipInfo.labels.autoRefresh')}
{countdown.type === 'countdown'
? `: ${countdown.remainingSeconds}s`
: '...'}
</Typography>
<FormControlLabel
sx={{ m: 0 }}
control={
<Switch
size="small"
checked={autoRefresh}
onChange={(_, checked) => setAutoRefresh(checked)}
/>
}
label={
<Typography variant="caption">
{t('home.components.ipInfo.labels.autoRefresh')}
{autoRefresh &&
(countdown.type === 'countdown'
? `: ${countdown.remainingSeconds}s`
: '...')}
</Typography>
}
/>
<Typography
variant="caption"
sx={{
@@ -397,9 +397,9 @@ export const RulesEditorViewer = (props: Props) => {
return
}
setPrependSeq(obj?.prepend || [])
setAppendSeq(obj?.append || [])
setDeleteSeq(obj?.delete || [])
setPrependSeq((obj?.prepend || []).filter((rule) => rule !== null))
setAppendSeq((obj?.append || []).filter((rule) => rule !== null))
setDeleteSeq((obj?.delete || []).filter((rule) => rule !== null))
hasLoadedSeqConfigRef.current = true
}, [property])
@@ -417,9 +417,9 @@ export const RulesEditorViewer = (props: Props) => {
hasLoadedSeqConfigRef.current = true
startTransition(() => {
setPrependSeq(obj?.prepend ?? [])
setAppendSeq(obj?.append ?? [])
setDeleteSeq(obj?.delete ?? [])
setPrependSeq((obj?.prepend ?? []).filter((rule) => rule !== null))
setAppendSeq((obj?.append ?? []).filter((rule) => rule !== null))
setDeleteSeq((obj?.delete ?? []).filter((rule) => rule !== null))
})
setVisualization(true)
}
+1 -1
View File
@@ -608,7 +608,7 @@ function NormalProxyGroups(props: { mode: string }) {
/>
</DragDropProvider>
{mode === 'rule' && (
{(mode === 'rule' || mode === 'global') && (
<ProxyGroupNavigator
proxyGroupNames={proxyGroupNames}
onGroupLocation={handleGroupLocationByName}
+2 -2
View File
@@ -211,7 +211,7 @@ export const useRenderList = (
if (!proxyView) return []
if (isChainMode)
return selectedGroup ? [selectedGroup] : [CHAIN_DELAY_GROUP]
return mode === 'rule' || mode === 'script'
return mode === 'rule' || mode === 'global'
? proxyView.groups.map(({ name }) => name)
: proxyView.global
? [proxyView.global.name]
@@ -257,7 +257,7 @@ export const useRenderList = (
}))
}
const useRule = mode === 'rule' || mode === 'script'
const useRule = mode === 'rule' || mode === 'global'
const renderGroups = useRule
? proxyView.groups
: proxyView.global === null
+1
View File
@@ -36,6 +36,7 @@
"yes": "Yes",
"no": "No",
"failed": "Failed",
"risky": "غير مؤكد",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Unterstützt",
"no": "Nicht unterstützt",
"failed": "Test fehlgeschlagen",
"risky": "Ungewiss",
"completed": "Prüfung abgeschlossen",
"disallowedIsp": "Nicht zugelassener Internetdienstanbieter",
"originalsOnly": "Nur Original",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Yes",
"no": "No",
"failed": "Failed",
"risky": "Risky",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Soportado",
"no": "No soportado",
"failed": "Prueba fallida",
"risky": "Dudoso",
"completed": "Detección completada",
"disallowedIsp": "Proveedor de servicios de Internet no permitido",
"originalsOnly": "Solo originales",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Yes",
"no": "No",
"failed": "Failed",
"risky": "نامشخص",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Yes",
"no": "No",
"failed": "Failed",
"risky": "Tidak pasti",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
+1
View File
@@ -36,6 +36,7 @@
"yes": "サポートする",
"no": "サポートしない",
"failed": "テストに失敗しました。",
"risky": "要確認",
"completed": "検査完了",
"disallowedIsp": "許可されていないインターネットサービスプロバイダー",
"originalsOnly": "オリジナルのみ",
+1
View File
@@ -36,6 +36,7 @@
"yes": "예",
"no": "아니오",
"failed": "실패",
"risky": "확인 필요",
"completed": "완료",
"disallowedIsp": "허용되지 않는 ISP",
"originalsOnly": "오리지널만",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Sim",
"no": "Não",
"failed": "Falhou",
"risky": "Incerto",
"completed": "Concluído",
"disallowedIsp": "ISP não permitido",
"originalsOnly": "Somente originais",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Да",
"no": "Нет",
"failed": "Ошибка",
"risky": "Неясно",
"completed": "Завершено",
"disallowedIsp": "Провайдер заблокирован",
"originalsOnly": "Только Originals",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Evet",
"no": "Hayır",
"failed": "Başarısız",
"risky": "Belirsiz",
"completed": "Tamamlandı",
"disallowedIsp": "İzin Verilmeyen ISP",
"originalsOnly": "Yalnızca Orijinaller",
+1
View File
@@ -36,6 +36,7 @@
"yes": "Yes",
"no": "No",
"failed": "Failed",
"risky": "Билгесез",
"completed": "Completed",
"disallowedIsp": "Disallowed ISP",
"originalsOnly": "Originals Only",
+1
View File
@@ -36,6 +36,7 @@
"yes": "支持",
"no": "不支持",
"failed": "测试失败",
"risky": "存疑",
"completed": "检测完成",
"disallowedIsp": "不允许的 ISP",
"originalsOnly": "仅限原创",
+1
View File
@@ -36,6 +36,7 @@
"yes": "支援",
"no": "不支援",
"failed": "測試失敗",
"risky": "存疑",
"completed": "檢測完成",
"disallowedIsp": "不允許的網際網路服務供應商",
"originalsOnly": "僅限原創",
+1 -1
View File
@@ -55,7 +55,7 @@ const wrapCssInjectionWithScope = (css?: string) => {
return null
}
const scopeRoot = CSS_INJECTION_SCOPE_ROOT
const scopeLimit = CSS_INJECTION_SCOPE_LIMIT
const scopeLimit = `${CSS_INJECTION_SCOPE_LIMIT} > *`
const scopedBlock = `@scope (${scopeRoot}) to (${scopeLimit}) {
${css}
}`
+4 -2
View File
@@ -40,6 +40,7 @@ const STATUS_LABEL_KEYS: Record<string, string> = {
Yes: 'tests.statuses.test.yes',
No: 'tests.statuses.test.no',
Failed: 'tests.statuses.test.failed',
Risky: 'tests.statuses.test.risky',
Completed: 'tests.statuses.test.completed',
'Disallowed ISP': 'tests.statuses.test.disallowedIsp',
'Originals Only': 'tests.statuses.test.originalsOnly',
@@ -196,7 +197,7 @@ const UnlockPage = () => {
if (status === 'Pending') return 'default'
if (status === 'Yes') return 'success'
if (status === 'No') return 'error'
if (status === 'Soon') return 'warning'
if (status === 'Soon' || status === 'Risky') return 'warning'
if (status.includes('Failed')) return 'error'
if (status === 'Completed') return 'info'
if (
@@ -222,7 +223,8 @@ const UnlockPage = () => {
const getStatusBorderColor = (status: string) => {
if (status === 'Yes') return theme.palette.success.main
if (status === 'No') return theme.palette.error.main
if (status === 'Soon') return theme.palette.warning.main
if (status === 'Soon' || status === 'Risky')
return theme.palette.warning.main
if (status.includes('Failed')) return theme.palette.error.main
if (status === 'Completed') return theme.palette.info.main
return theme.palette.divider