Commit Graph
729 Commits
Author SHA1 Message Date
Qiu Yuzhou 0d0e74d59c feat(ui): menu-bar-only form with scene-based main window
Replace the hybrid form (LSUIElement + runtime activation-policy
switching + AppKit-owned NSWindow) with a plain SwiftUI Window scene:
the app stays accessory at all times, launch presents the workspace
window via defaultLaunchBehavior(.presented), and the status menu
reopens it after close via openWindow. Drop AppDelegate and the
window-opening adapter and intents; WorkspaceRoute keeps navigation
state only, with the scene id defined there. Documented in ADR 0016
and CONTEXT.md.
2026-09-28 19:18:27 +08:00
Qiu Yuzhou c9384d2de9 fix(ui): fix filename for ServersView 2026-09-28 18:32:42 +08:00
Qiu Yuzhou 4f67c4a73c fix(ui): align add menu with sidebar toggle 2026-09-28 12:44:56 +08:00
Qiu Yuzhou 63e33724f7 feat(ui): consolidate workspace add actions 2026-09-28 11:45:58 +08:00
Qiu Yuzhou 92040baef0 fix(ng2): remove direct clipboard import shortcut 2026-09-28 10:54:34 +08:00
Qiu Yuzhou 67ced2674a fix(ng2): use canonical app name 2026-09-28 10:24:06 +08:00
Qiu Yuzhou 5abb6c6033 build: migrate NG2 to Swift 6 and clear warnings 2026-09-28 08:44:53 +08:00
Qiu Yuzhou 2946c34ddb refactor(settings): remove whole-form draft workflow 2026-09-28 08:09:35 +08:00
Qiu Yuzhou 8fd451b22e style(settings): simplify section headings 2026-09-28 07:47:22 +08:00
Qiu Yuzhou 93af9806df feat(settings): edit system proxy exceptions
Keep user entries separate from app-fixed ACL bypass rules, and fix
simple-hostname exclusion while the app owns system proxy settings.
2026-09-27 23:44:21 +08:00
Qiu Yuzhou cf1834a775 feat(ng2): add four-way listener mode 2026-09-27 20:58:54 +08:00
Qiu Yuzhou 5a05b8a661 refactor(ng2): use sslocal runtime defaults 2026-09-27 17:51:34 +08:00
Qiu Yuzhou 59665f9b76 fix(settings): align SOCKS label wording 2026-09-27 17:24:25 +08:00
Qiu Yuzhou b99087532c fix(settings): refine port editor alignment 2026-09-27 17:21:23 +08:00
Qiu Yuzhou e62ce007d7 feat(settings): save proxy ports independently 2026-09-27 17:07:47 +08:00
Qiu Yuzhou 8013e7942c feat(gitignore): add .gitignore for CodeGraph data files 2026-09-27 16:08:13 +08:00
Qiu Yuzhou 88af8bfdf2 feat(system-proxy): write HTTP/HTTPS alongside SOCKS
Apps honoring only the HTTP proxy family were never proxied under
the SOCKS-only projection. Point system HTTP and HTTPS at the
always-on sslocal HTTP inbound (CONNECT); reject documents lacking
that inbound instead of aiming at port 0. See ADR 0012.
2026-09-27 15:49:03 +08:00
Qiu Yuzhou effae048b8 feat(settings): remove holistic preferences reset
Not every preference warrants a reset; future resets are per
configuration item, added only where a field truly needs one (tracked
in #69). The whole chain goes: the 恢复默认 button and its confirmation
alert in SettingsView, the SettingsWorkflow reset/confirm/cancel
commands and the confirmation machinery they existed for (the
SettingsConfirmation enum had a single case), the
SettingsCommitting.resetPreferences seam entry, the runtime
resetPreferences() and ProxySettingsFileStore.reset() with its
file-rollback helpers and the rollbackFailed store error.

CONTEXT.md drops the three stale reset phrases and now records the
domain decision: no restore-all-factory-defaults action for
preferences; a reset, where it exists, is scoped to one configuration
item. ADR-0004/0005/0011 carry amendment notes since their reset
sentences describe a removed capability (0004's also predate the
PAC removal).
2026-09-27 13:29:32 +08:00
Qiu Yuzhou 8aedf8c42e perf(acl): per-variant ACL files behind a stable active link
Stop rewriting the whole ACL on every proxy-mode switch: materialize one
file per routing shape (acl-<summary>.ini), keep acl-active.ini as a stable
symlink the contract points at, and skip unchanged writes via a digest
manifest. The contract carries only path/summary/sha256 — the agent checks
the link stays inside the runtime directory and never reads ACL bytes.

Agent switch now defaults to off (no server config on a fresh install) and
survives preferences reset, which also returns any held system proxy and
converges a still-running agent onto the factory runtime. See ADR-0011.
2026-09-27 03:14:38 +08:00
Qiu Yuzhou 851660f1c6 test: split real-process smoke into opt-in target
Real sslocal smoke (11 tests, ~27s) sat on every task test as the
slow tail. Tests/Smoke now builds as a separate target behind the
ShadowsocksX-NG2Smoke scheme; task test:smoke runs it on demand and
the README release gate requires it green next to packaging-gate.

SignalRecorder also gains kill(_, 0) semantics (stale pids report
ESRCH): the GUI-restart test burned the 5s waitForWrapperExit
ceiling on pid 99999, now 0.18s. Default lane: 617 tests ~40s.
2026-09-27 01:11:48 +08:00
Qiu Yuzhou e25e211165 perf(test): default fixtures to short launch health window
Negative-path tests with a refusing FakeProbe each burned the full
production 15s launch health window; the three 30s tests run two
windows (activate + setAgentEnabled). Five tests accounted for 120s
of the 188.5s suite.

All test-side controller constructors now inject 0.05s; production
default stays 15s. Deterministic fakes exercise the same timeout
presentation path, so the suite drops to 71.4s with no behavior
change. Remaining slow tests are real-process smoke tests.
2026-09-27 00:54:10 +08:00
Qiu Yuzhou d6ce1817dc test(fixture): allocate free listen ports instead of colliding with the running app
ProxyRuntimeFixture.makeDocument 固定 socks 11086 / http 11087,与 app
默认监听相同:宿主 app 在跑时其 sslocal 占住两端口,wrapper 健康探测
误判「监听已建立」,testListenNotEstablishedWithinDeadline 必红,
ACL 直连实例也会误发布运行回执。改为测试进程内一次性分配空闲端口对
(静态 let,非逐调用分配——「仅服务器变化」语义依赖两次默认构造端口
一致);显式 localPort 调用方行为不变。分配失败 loud fail,不再回退
到冲突端口。
2026-09-27 00:32:31 +08:00
Qiu Yuzhou e01f6e96cc fix(agent): force builtin DNS resolver for sslocal 2026-09-27 00:16:11 +08:00
Qiu Yuzhou 2acaef60c8 fix(agent): register LaunchAgent via launchctl bootstrap with absolute path
SMAppService does not resolve relative ProgramArguments paths reliably
when the app runs from DerivedData or other non-/Applications locations
(launchd reports inferred program + partial import and rejects spawn
with EX_CONFIG). The same binary and plist format work correctly when
registered via launchctl bootstrap with an absolute path.

Write the plist to ~/Library/LaunchAgents/ with the wrapper's absolute
path and register via launchctl bootstrap. Keep SMAppService only for
reading BTM approval status.
2026-09-26 23:32:42 +08:00
Qiu Yuzhou d08f0583d7 fix(agent): write absolute ProgramArguments path before SMAppService registration
SMAppService does not reliably resolve relative ProgramArguments paths
for launchd (launchd reports inferred program + partial import and
rejects spawn with EX_CONFIG). Rewrite the plist's ProgramArguments[0]
to the wrapper's absolute path just before register().
2026-09-26 23:13:26 +08:00
Qiu Yuzhou 354b0fb59e fix(agent): correct LaunchAgent ProgramArguments path for SMAppService
SMAppService resolves ProgramArguments relative to the app bundle's
Contents/ directory, not the bundle root. The plist used
Contents/MacOS/ShadowsocksX-NG2Agent which resolved to a nonexistent
Contents/Contents/MacOS/... path, causing launchd to report
EX_CONFIG / spawn failed and the wrapper never started.

Change to MacOS/ShadowsocksX-NG2Agent so launchd finds the binary.
2026-09-26 23:04:26 +08:00
Qiu Yuzhou 973e8dd3b0 fix(proxy): tolerate legacy PAC preferredMode when decoding settings
An old settings.json with preferredMode "pac" made decodeIfPresent throw
on ProxyModeKind, which marked the whole snapshot unreadable and refused
to deploy. Decode the raw string and fall back to rule mode for unknown
values so retained fields (ports, timeout, exceptions) survive.

Closes the startup failure reported after #67.
2026-09-26 22:44:24 +08:00
Qiu Yuzhou a8cd6262c2 feat(proxy): remove PAC and accept ACL system proxy path
Remove the PAC product feature entirely: PAC HTTP service, port, health
probe, runtime extension, PAC URL system proxy projection, PAC rule
editing, and GFWList URL settings. The product now exposes rule / global /
direct modes only, all projected as a local SOCKS system-proxy target.

First-run defaults are rule + proxyWhenUnmatched, agent on, system proxy
off. Homepage and status menu share one ProxyControlSnapshot showing the
three modes and rule sub-options. Manual servers, subscriptions, Legacy
import, and independent SOCKS/HTTP inbounds are retained.

Fix: include verbose in SslocalListenFingerprint so a verbose-only
contract change restarts sslocal instead of leaving RUST_LOG stale.

Revise CONTEXT.md vocabulary, ADR-0007, and ADR-0009. Record GFWList
exception shadowing, IPv6 system-exception, DNS, and traffic-coverage
boundaries. Add host acceptance evidence document.

Closes #67
2026-09-26 21:58:12 +08:00
Qiu Yuzhou 9b32400b24 feat(proxy): add custom rule storage, validation, and ACL merge
Persist user custom rules (exact domain, domain suffix, IPv4/IPv6 CIDR)
with stable identity and source metadata. Reject rules that conflict
with the fixed local scope or are shadowed by sslocal domain-proxy /
IP-direct priority, with explainable reasons. Merge valid custom rules
into both rule sub-modes ACL; global and direct modes do not load them.
Rule content changes recompile ACL via full restart with rollback.
Diagnostics expose only count and content-version summary.
2026-09-26 20:27:56 +08:00
Qiu Yuzhou 4a4535b947 feat(proxy): add GFWList proxy candidates for direct-when-unmatched rule mode 2026-09-26 19:20:09 +08:00
Qiu Yuzhou 7a59ca7827 feat(proxy): add China IPv4 CIDR direct candidates to rule mode
Pin gaoyifan/china-operator-ip china.txt and compile its IPv4 CIDRs into
the proxy_all ACL alongside China domain candidates. Explicit maintainer
update normalizes and deduplicates CIDRs and blocks replacement on
abnormal format, corruption, scale change, or all-invalid input.

Closes #64
2026-09-26 18:16:44 +08:00
Qiu Yuzhou 827fb98be3 refactor(ng2): fix all swiftlint warnings
77 → 0 warnings. Split oversized functions and files: controller and
agent extension files, standalone RuleSet/ProxySettingsFileStore/
SslocalListenSettings/LegacyImportService, test classes into topic
extensions. Regroup activation options into RuntimeDocumentOptions and
converter metadata into Provenance.

Runtime controller DI seams widen private → internal so the extension
files stay cohesive; new controller files are registered in the
architecture guard exemption list; Agent target adds RuleSet and
SslocalListenSettings to project.yml. Test JSON literals wrap inside
the string literal only.
2026-09-26 17:13:31 +08:00
Qiu Yuzhou 120116627a feat(proxy): add built-in China domain rules and rule mode
Introduce a source-independent rule model (action, domain/CIDR match,
source identity, conflict metadata) and ship a pinned geolocation-cn
snapshot produced by an explicit maintainer update. Rule mode with
proxy-when-unmatched compiles China domains as direct candidates into a
proxy_all ACL; the sub-option is selectable and persisted from home and
the status menu. Ordinary builds only read the local snapshot and fail
when it is missing or corrupt.
2026-09-26 15:25:45 +08:00
Qiu Yuzhou f2e9cff69d feat(proxy): add ACL-backed global mode with local bypass
Global mode now deploys a minimal proxy_all ACL carrying only the fixed
local safety rules, so public targets default through Shadowsocks while
loopback/RFC1918/link-local/ULA/localhost/*.local/no-dot hosts stay
direct for SOCKS, HTTP, and LAN-sharing clients. System proxy exceptions
include that fixed scope; IPv6 system-exception limits are documented
with the ACL as the routing guarantee. Mode switches keep the
full-restart and rollback path.

Closes #62
2026-09-26 12:33:35 +08:00
Qiu Yuzhou 39e25477bb feat(proxy): add ACL-backed direct mode 2026-09-26 11:46:45 +08:00
Qiu Yuzhou 41379bb1cb fix(ng2): review fixes for agent/system-proxy switch split
- setAgentEnabled(true) 在意图已开启且未达健康态(off/启动失败/服务
  失败)时执行重收敛,成为失败态的显式重试入口;健康运行中不做无谓
  的注销重拉
- StatusMenuModel.Summary 增加 systemProxyStateLabel 短标签,去掉
  HomeView 对呈现文本的字符串剥离耦合
- 控制器头注释改为如实描述:launchd/契约动作序列在 ProxyRuntimePlan,
  系统代理门禁收敛决策留在控制器(issue #60)
- 新增重试路径测试
2026-09-26 00:55:52 +08:00
Qiu Yuzhou 398e8d84b6 feat(ng2): split agent and system proxy switches
实现 #60(ACL 1/8):agent 意图与系统代理意图拆为两个独立开关,
首次运行默认 agent 开、系统代理关;显式关闭 agent 持久化并在 GUI
重启后生效;关闭系统代理只恢复 NG2 持有的系统设置,本地 SOCKS/HTTP
继续监听;关闭 agent 先恢复系统设置再停止监听。无活动目标时 agent
以空服务器列表契约提供本地监听(isWellFormed 放行空 servers,上游
sslocal v1.25.0 接受);需要代理出口的模式在活动目标缺失或端点不健
康时系统代理保持待应用,条件恢复后随下次收敛自动应用。首页、状态菜
单与侧栏状态卡经同一代理控制快照展示/操作 agent 意图/运行状态与系
统代理意图/实际应用及 typed 错误。测试覆盖启动顺序、持久化、健康门
禁、ownership 冲突、无效活动目标与 GUI 重同步(含真实 sslocal 空监
听回环证据);替身测试不表述为真实系统写入验证。
2026-09-26 00:38:28 +08:00
Qiu Yuzhou 76b19ba935 feat(ng2): remove HTTP proxy toggle, inbound always on
The toggle only gated a convenience endpoint: system proxy never used
the HTTP port, which serves the terminal export line and LAN proxy.
Stored httpProxyEnabled:false is silently ignored, no migration.
Proxy-endpoint settings rows lose their subtitles; glossary term is
now the permanent "HTTP proxy inbound".
2026-09-25 03:57:06 +08:00
Qiu Yuzhou 2400c7c81d feat(ng2): window title follows active destination
- window.title tracks the destination via composition-root wiring
  into WorkspaceWindowOpeningAdapter.bindTitle; drop the static
  "ShadowsocksX-NG 2.0" title
- delete the 28pt shell destinationHeader; subscription and
  diagnostics actions move into the unified toolbar
- settings save/reset stay with the form as a top action row in
  SettingsView; form-level commit actions read wrong in a toolbar
2026-09-25 03:18:59 +08:00
Qiu Yuzhou be48a39f64 fix(ng2): restore workspace window chrome
ad74b23 的手动创建的 NSWindow 省略了 Window scene 提供的 chrome。
如果没有真实的 NSToolbar,NavigationSplitView bridge 会安装一半:
工具栏玻璃覆盖了部分标题,且没有安全区域插入(safe-area inset)。
默认的 titlebar 会将标题截断为 "ShadowsocksX-N…"。添加空的统一
toolbar 以及 fullSizeContentView 和透明 titlebar,以匹配 scene
的外观。
2026-09-25 01:38:29 +08:00
Qiu Yuzhou 1c5e4e5e23 feat(docs): add research on sslocal password handling via environment variables 2026-09-25 00:51:05 +08:00
Qiu Yuzhou 4fcfa1242c fix(ng2): disable SwiftLint identifier name warnings in SubscriptionRefreshStatus 2026-09-25 00:05:22 +08:00
Qiu Yuzhou b8f80492e7 style(editorconfig): add configuration for Swift files 2026-09-25 00:02:07 +08:00
Qiu Yuzhou ad74b23cb6 fix(ng2): AppKit-owned workspace window opens at launch
三重根因,逐一实测排除:①MenuBarExtra 菜单内容视图首次点开才挂载,
launch intent 挂在内容上等于从不执行(此前 legacy 导入自动开窗同样从未
生效),改挂到启动即挂载的标签视图;②SwiftUI openWindow 在 MenuBarExtra
上下文实测不创建场景窗口,且 LSUIElement 应用无场景启动呈现机制——主窗
口改为 AppKit 直控 NSWindow(NSHostingView 承载主 workspace),关闭仅
orderOut 复用,开窗不依赖场景时序;③resyncOnLaunch 的 launchd 往返
await 挡在开窗前,改为先开窗后 resync。装配抽成 AppComposition(init
体量回到 lint 基线)。真机验证:启动即出现主窗口,进程前台形态,
SwiftLint/全量测试通过。
2026-09-24 23:51:48 +08:00
Qiu Yuzhou a0137651fa fix(ng2): present workspace window on every launch
登录启动判定没有可靠的系统 API,uptime 启发式与 launch-at-login 语义
不严格对应(logout/login 不重启时检测不到,启用登录项后开机超窗口期的
手动启动会被误判静默)。按产品决定简化:不再区分启动来源,每次启动都
开主窗口;混合形态(开窗 .regular / 关最后一个窗 .accessory)不变。
2026-09-24 23:07:12 +08:00
Qiu Yuzhou cb4ad5a87d feat(ng2): hybrid app form with foreground workspace window 2026-09-24 22:53:11 +08:00
Qiu Yuzhou ab6acf8912 refactor(ng2): remove UDP relay preference 2026-09-24 22:00:58 +08:00
Qiu Yuzhou 620c929450 docs(research): survey proxy rule sources 2026-09-24 21:41:33 +08:00
Qiu Yuzhou fb6be460b8 docs(research): assess sslocal ACL as PAC alternative
Document routing, reload, DNS, UDP, and compatibility boundaries
before considering an NG2 runtime migration.
2026-09-24 21:40:51 +08:00
Qiu Yuzhou 3842bdc97c fix(ng2): remove obsolete settings menu item 2026-09-24 21:03:28 +08:00