From 168ff2e93ed3277b0f4b9cc2a6c11ef2fa16ded6 Mon Sep 17 00:00:00 2001 From: Qiu Yuzhou Date: Sun, 25 Oct 2026 12:26:00 +0800 Subject: [PATCH] feat(rules): reorder presets and default to GFWList --- .../ProxyControlWorkflow/ProxyControlWorkflow.swift | 2 +- .../App/Application/RulesWorkflow/RulesWorkflow.swift | 2 +- .../App/Application/RulesWorkflow/RulesWorkspaces.swift | 2 +- ShadowsocksX-NG2/Domain/Rules/BuiltinRuleCatalog.swift | 2 +- .../Domain/Settings/ProxyControlPreferences.swift | 2 +- ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift | 4 ++-- ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift | 2 +- .../Tests/Settings/ProxyControlPreferencesTests.swift | 2 +- docs/adr/0051-explicit-rule-preset-modeling.md | 6 +++--- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift index b855aff9..895013c5 100644 --- a/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift +++ b/ShadowsocksX-NG2/App/Application/ProxyControlWorkflow/ProxyControlWorkflow.swift @@ -74,7 +74,7 @@ struct ProxyControlSnapshot: Equatable, Sendable { /// 当前已应用的代理模式(持久化成功的模式)。 let proxyMode: ProxyMode /// 规则模式子选项:未匹配默认动作;仅规则模式呈现。 - var rulePreset: RulePreset = .chinaDirect + var rulePreset: RulePreset = .gfwlist var ruleDefaultAction: RuleDefaultAction { rulePreset.defaultAction } /// 可用模式由 Domain 单点策略投影;UI 不自行判断可用性。 let availableModes: [ProxyMode] diff --git a/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift b/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift index 373feda9..ab0c5f9a 100644 --- a/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift +++ b/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkflow.swift @@ -23,7 +23,7 @@ final class RulesWorkflow: ObservableObject { private let analysisDelay: @Sendable () async throws -> Void init( - preset: RulePreset = .chinaDirect, + preset: RulePreset = .gfwlist, loadCustom: (@Sendable () throws -> [CustomRule])? = nil, loadDocument: (@MainActor () throws -> CustomRuleDocument)? = nil, commitDocument: (@MainActor (CustomRuleDocument) async -> RuleDocumentCommit)? = nil, diff --git a/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkspaces.swift b/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkspaces.swift index 0e69dca8..86edbae4 100644 --- a/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkspaces.swift +++ b/ShadowsocksX-NG2/App/Application/RulesWorkflow/RulesWorkspaces.swift @@ -4,7 +4,7 @@ import Foundation /// Each preset owns an unchanged workflow, including its in-flight operations. @MainActor final class RulesWorkspaces: ObservableObject { - @Published var selectedPreset: RulePreset = .chinaDirect + @Published var selectedPreset: RulePreset = .gfwlist @Published private(set) var reportSource: RulesSourceSnapshot? private let workflows: [RulePreset: RulesWorkflow] private var reports: Set = [] diff --git a/ShadowsocksX-NG2/Domain/Rules/BuiltinRuleCatalog.swift b/ShadowsocksX-NG2/Domain/Rules/BuiltinRuleCatalog.swift index bcfc8d7a..9898cc41 100644 --- a/ShadowsocksX-NG2/Domain/Rules/BuiltinRuleCatalog.swift +++ b/ShadowsocksX-NG2/Domain/Rules/BuiltinRuleCatalog.swift @@ -117,8 +117,8 @@ actor BuiltinRuleSnapshots { /// The same policy supplies runtime candidates and the management collection. enum RulePreset: String, Codable, CaseIterable, Hashable, Sendable { - case chinaDirect = "china-direct" case gfwlist = "gfwlist" + case chinaDirect = "china-direct" case chinaProxy = "china-proxy" init(legacy action: RuleDefaultAction) { diff --git a/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift b/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift index 4b57b1d9..42178d37 100644 --- a/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift +++ b/ShadowsocksX-NG2/Domain/Settings/ProxyControlPreferences.swift @@ -23,7 +23,7 @@ struct ProxyControlPreferences { } let legacy = defaults.string(forKey: "proxyControl.ruleDefaultAction") - .flatMap(RuleDefaultAction.init(rawValue:)) ?? .proxyWhenUnmatched + .flatMap(RuleDefaultAction.init(rawValue:)) ?? .directWhenUnmatched return RulePreset(legacy: legacy) } nonmutating set { defaults.set(newValue.rawValue, forKey: "proxyControl.rulePreset") } diff --git a/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift b/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift index 2d0288d9..d4b21acc 100644 --- a/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift +++ b/ShadowsocksX-NG2/Domain/Settings/ProxyRuntimeSettings.swift @@ -11,7 +11,7 @@ struct ProxyRuntimeSettings: Equatable, Sendable { /// The persisted current mode: the mode selector's choice survives GUI /// restarts. First run defaults to rule mode. var preferredMode: ProxyModeKind - /// 规则模式预设,出厂为中国大陆目标直连。 + /// 规则模式预设,首次启动默认为仅 GFW 列表内目标代理。 var rulePreset: RulePreset var ruleDefaultAction: RuleDefaultAction { get { rulePreset.defaultAction } @@ -27,7 +27,7 @@ struct ProxyRuntimeSettings: Equatable, Sendable { listen: ProxyListeningConfiguration = ProxyListeningConfiguration(), proxyExceptions: String = ProxyRuntimeSettings.defaultProxyExceptions, preferredMode: ProxyModeKind = .rule, - ruleDefaultAction: RuleDefaultAction = .proxyWhenUnmatched, + ruleDefaultAction: RuleDefaultAction = .directWhenUnmatched, rulePreset: RulePreset? = nil, agentEnabled: Bool = false, systemProxyEnabled: Bool = false diff --git a/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift b/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift index 77960c8c..fcf80a65 100644 --- a/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift +++ b/ShadowsocksX-NG2/Tests/Rules/RulePresetWorkflowTests.swift @@ -93,7 +93,7 @@ extension RulePresetWorkflowTests { let defaults = try #require(UserDefaults(suiteName: suite)) defer { defaults.removePersistentDomain(forName: suite) } let preferences = ProxyControlPreferences(defaults: defaults) - #expect(preferences.rulePreset == .chinaDirect) + #expect(preferences.rulePreset == .gfwlist) defaults.set("direct-when-unmatched", forKey: "proxyControl.ruleDefaultAction") #expect(preferences.rulePreset == .gfwlist) preferences.rulePreset = .chinaProxy diff --git a/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift b/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift index 28c2ddba..e84390b6 100644 --- a/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift +++ b/ShadowsocksX-NG2/Tests/Settings/ProxyControlPreferencesTests.swift @@ -16,7 +16,7 @@ struct ProxyControlPreferencesTests { preferences: ProxyControlPreferences(defaults: defaults)) #expect(settings.preferredMode == .rule) - #expect(settings.ruleDefaultAction == .proxyWhenUnmatched) + #expect(settings.ruleDefaultAction == .directWhenUnmatched) #expect(!settings.agentEnabled) #expect(!settings.systemProxyEnabled) #expect(settings.proxyExceptions.isEmpty) diff --git a/docs/adr/0051-explicit-rule-preset-modeling.md b/docs/adr/0051-explicit-rule-preset-modeling.md index c7426915..828008be 100644 --- a/docs/adr/0051-explicit-rule-preset-modeling.md +++ b/docs/adr/0051-explicit-rule-preset-modeling.md @@ -1,10 +1,10 @@ # 将隐式路由策略显式建模为规则预设 -**状态**:已接受,已实现(2026-10-24)。 +**状态**:已接受,已实现(2026-10-24;默认项与展示顺序于 2026-10-25 调整)。 ## 决定 -规则、全局、直连仍是代理模式。规则模式内使用三种规则预设:“中国大陆目标直连”“仅GFW列表内目标代理”“中国大陆目标代理”。预设一起确定内置来源、命中行动和默认动作,替代原来的默认动作选择入口,不另设独立默认动作开关。新预设复用中国域名、`.cn` 和中国 IPv4 数据,将其用于代理,其余目标默认直连;固定本地目标仍直连,不维护视频专用集合,也不承诺平台内容可播放。 +规则、全局、直连仍是代理模式。规则模式内使用三种规则预设,展示顺序为“仅GFW列表内目标代理”“中国大陆目标直连”“中国大陆目标代理”。预设一起确定内置来源、命中行动和默认动作,替代原来的默认动作选择入口,不另设独立默认动作开关。新预设复用中国域名、`.cn` 和中国 IPv4 数据,将其用于代理,其余目标默认直连;固定本地目标仍直连,不维护视频专用集合,也不承诺平台内容可播放。 每项预设独立拥有自定义规则集合和规则停用选择。自定义规则保留自身行动,沿用现有冲突机制,用于补充规则而不保证覆盖内置规则。预设内继续使用“匹配条件+行动”的规则身份及跨来源停用语义,修改不影响其他预设。不增加共享自定义层或同一条目的多预设归属。 @@ -16,7 +16,7 @@ 地址测试依据所选预设的完整规则和默认动作解释代理或直连结论,区分规则命中与未匹配时采用默认动作,并展示决定行动的规则。结果不受搜索、过滤、实际代理模式或运行状态影响,不要求该预设正在使用。测试输入和结果按预设独立保留;测试不解析 DNS、不测试连通性,域名测试明确说明未检查 DNS 解析后的 IP 规则。 -保留原出厂默认及已有的路由选择映射。旧自定义规则和规则启用/禁用用户配置均不迁移,不按行动拆分,也不复制到新预设。新预设工作区不继承旧自定义条目或旧停用选择,按默认启用状态初始化;发版时明确说明这两项不兼容变化。 +没有显式保存的预设或旧版默认动作偏好时,默认选第一项“仅GFW列表内目标代理”;显式保存的预设继续生效,旧版默认动作继续映射到等价预设。旧自定义规则和规则启用/禁用用户配置均不迁移,不按行动拆分,也不复制到新预设。新预设工作区不继承旧自定义条目或旧停用选择,按默认启用状态初始化;发版时明确说明这两项不兼容变化。 ## 取舍与替代