打磨(P2): 渲染清理 + ech/mux 共享 zod 片段 + cronet/route/版本 manifest 加固#55
Merged
Conversation
阻止 QUIC 的 reject(UDP 443) 规则原本只放在路由末尾,但 smart 模式下 "google 关键词→代理"和"geosite-!cn→代理"两条规则在它之前且是终止规则, 国外 QUIC 在被路由到代理时即终止,走不到末尾的 reject —— 导致 smart 模式 下 Block QUIC 只拦到不在任何 geosite 列表里的未知域名 QUIC。(global 模式 无 geosite 规则,末尾 reject 可兜全部,不受影响。) 改为给每条"→代理"规则各配对一条放在其之前的 UDP-443 reject,使代理向 QUIC 在被路由前就回退 TCP;CN 直连规则(geosite-cn/geoip-cn)不配对,故 CN/直连 QUIC 保持直连不拦。末尾兜底 reject 保留(global 模式 + smart 模式 未被 google/!cn 命中的代理向 QUIC)。三条 reject 共用新增的 blockProxyQuic 协议 guard(跳过 hysteria2/tuic,其传输走 QUIC dial-server)。 注: 上游合并 dododook#45 时未包含此 smart 模式修复,故对 v3.5.1 重新提交。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
两个问题合并修复:
1) 订阅解析丢节点(数据缺失 bug)
SubscriptionService 的 sing-box outbound 白名单 SUPPORTED 漏掉 naive,
导致订阅里的 naive 节点被静默跳过——真实订阅(29 个 naive 节点)拉取后
全部消失。补入 naive 分支:解析 username/password/tls,并按 quic 字段
推导 useHttp3。
2) naive HTTP/3(QUIC)支持
sing-box 的 naive outbound 通过 quic:true 走 h3(QUIC/UDP) 传输,对应
服务端 `--listen=quic://`;此前 FlowZ 从不下发该字段,naive-h3 节点被
当成 H2/TCP 拨号。新增 NaiveSettings.useHttp3:
- 生成侧:useHttp3 时给 naive outbound 注入 quic:true;
- blockQuic guard:naive-h3 属 UDP-transport(QUIC dial-server),
reject UDP443 会经 strict_route 回流误杀其传输,故并入跳过集合
(原仅 hysteria2/tuic)。抽出 isUdpTransportNode 统一判定;
- UI:naive 表单新增 HTTP/3 开关 + 中英文案;
- 类型:ServerConfig.naiveSettings、SingBoxOutbound.quic、订阅侧
SingboxOutbound.username/quic。
验证:sing-box 1.13.3 check 差分确认 naive 的 quic 为合法 bool 字段
(unknown-field / 类型错误对照),quic:true 配置解析通过;tsc/eslint/
prettier/vite build 全绿,无新增类型错误。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
根因:blockQuic guard 原以"拨号传输"(isUdpTransportNode: hy2/tuic/naive-h3)判定跳过, 混淆了两个正交属性——决定"代理向 QUIC 会不会黑洞"的是"节点能否中继客户端 UDP",而非 节点拨号用什么传输。naive 拨号虽可为 h3(QUIC),但它只过 TCP(HTTP CONNECT)、不能中继 客户端 UDP,被错误归入"UDP-transport 跳过"集 → 开 blockQuic 时其代理向 QUIC 仍走代理 → naive 收不了 → 黑洞(影响用户 29 个 naive-h3 节点)。 实测定论(netns 隔离 TUN,auto_route+strict_route,抓包差分): - 带 `reject udp443` 时 hy2 拨号包仍正常逸出(14 vs baseline 10); - 客户端 udp443 被 reject(egress 0 vs 控制 udp444 1)。 证伪 PR-1"reject 经 strict_route 回流误杀拨号"的假设——拨号是 sing-box 进程自有 fwmark socket,受 auto_detect_interface 保护、绕过 route 规则,reject 只作用于 tun-inbound 客户端流。 改为按 isUdpRelayCapable 分流: - 不能中继(ssh/http/anytls/naive;naive 仅在 udp_over_tcp 下可中继,FlowZ 不下发该字段): 恒 reject 全部代理向 UDP(逼 QUIC 回退 TCP、其余 UDP 快速失败而非穿透 final 跌 direct 假死 30s); - 能中继(hy2/tuic 原生;vless/vmess/trojan/ss 经 UoT):仅 blockQuic 开时 reject 代理向 QUIC(udp443)。 连带修两个 bug: 1. naive 漏在原 tcpOnly 集 ['ssh','http','anytls'] → 代理向 UDP 黑洞;补入。 2. tcpOnly 全-UDP reject 原仅在路由末尾,smart 模式下 geosite-!cn→代理 为终止规则先命中、末尾 reject 够不着 → 给全-UDP reject 加 smart 配对(每条 →代理 之前),与 PR-1 对 blockQuic 的修法一致。 本提交取代 PR-1/PR-3 引入的 isUdpTransportNode 临时 guard;naive 生成侧注释同步更正。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 把"节点域名/IP → direct"豁免从"仅选中节点"扩展到"全部节点",使切换节点 / detour 前置 代理无需重生成配置即被豁免(为无缝切换铺路)。 - CDN 安全:域名节点仅用 domain + domain_suffix(靠 sniff 出的 SNI 精确匹配节点域名), 去掉过宽的 domain_keyword(会误匹配任意"含该域名串"的无关域名);不把域名预解析为共享 CDN IP(共享 IP 加直连会误伤同 IP 的被墙站点、且抗不住 IP 轮换)。仅 IP-literal 节点用 ip_cidr 排除(专用 IP、安全)。 已知遗留(待真机验证,已在代码注释标注):Windows TUN 的 resolvedIps 预解析仍把域名节点 解析为 IP 加 route_exclude_address——即 Windows 上的 CDN 风险点。是否可删需 Wintun 真机验证 "域名规则 + sniff 是否足以断回流环";若 Windows 确需 IP 级兜底,应仅对 IP-literal 节点保留。 无 Windows 环境暂不擅改。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
落实设计纠正——「禁 QUIC」的实现应只有一条 udp443 reject,且与选中节点无关。 删除的非预期实现: - rejectProxyUdpAll(reject 全部代理向 UDP):超出「禁 QUIC(443)」范围,会误砍能中继节点的 游戏/WebRTC 等非 QUIC UDP; - isUdpRelayCapable / TCP_ONLY_PROTOCOLS 节点分类; - 连带:blockQuic 不再跳过 hy2/tuic——concern-A 实测已证伪「reject 杀拨号」,无需跳过。 实测依据(netns 隔离 TUN,零宿主改动):UDP 路由到不能中继的出站时,sing-box 在出站层直接 拒绝(日志 "UDP is not supported by outbound"),不漏 direct、不黑洞。故非 QUIC 的代理向 UDP 无需路由层按节点固化 reject——FlowZ 旧注释「穿透 final 跌到 direct 假死 30s」系未验证的错误 前提(与 concern-A 同源)。 收敛后: - blockProxyQuic = config.blockQuic === true && !!selectedServer(节点无关); - smart 模式每条 →代理 前配对、以及末尾兜底,均只 reject udp443; - hy2/tuic 自身 QUIC 拨号受 fwmark 保护、不受影响(已实测)。 红利:路由配置与选中节点解耦 → 为 PR-5 selector 跨协议无缝热切换消除规则失配隐患。 本提交取代 PR-2 的「中继能力分类」模型(PR-2 未推送,最终提交时建议与 PR-1/PR-2 合并为一条 「节点无关 Block QUIC」PR)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
切节点从「重启 sing-box(断流)」改为 clash_api 热切换 selector,默认不断流(优雅切换)。 出站生成(generateOutbounds): - 改为生成【全部】节点出站(逐节点 try/catch 防单点拖垮)+ 一个 selector(tag=proxy-selector, 列出全部节点、default=当前选中、interrupt_exist_connections=用户开关)。 - 路由 final 与「→代理」规则、DNS detour 统一指向 proxy-selector,与选中节点解耦——PR-2 简化已 使 UDP 规则节点无关、PR-4 已豁免全节点域名,故跨协议热切换零规则失配。 切换链路: - CONFIG_CHANGED 监听 → switchMode 智能分派:当且仅当唯一变化是「切到运行中 selector 内的节点」 → clash_api `PUT /proxies/proxy-selector` 热切换、持久化 selectedServerId、不重启;失败退回重启; 其余变化(模式/端口/TUN/规则/节点集合/interrupt 开关)仍重启。canHotSwitch 严格校验。 开关: - UserConfig.interruptConnectionsOnSwitch(默认 false=优雅,连接保留至自然关闭)→ selector.interrupt_exist_connections;高级设置新增开关 + 中英文案。 连带修复 / 一致性: - 修复旧代码多跳 detour 链断裂(旧逻辑仅设选中节点 detour、链中节点 detour 丢失)→ 逐节点设 detour + 内联环检测(成环跳过、避免 sing-box 循环引用启动失败); - 删除因此变为死代码的 getDetourChain; - Shadow-TLS 节点反查改为按 idToTagMap 名称匹配(适配全节点名 tag); - 校正 blockQuic UI 与节点无关后端一致:移除对 hy2/tuic 的禁用(isQuicNode useMemo)+ 文案"不适用 Hysteria2/TUIC"(concern-A 实测已证伪跳过依据)。 验证:sing-box check 通过 selector(interrupt false/true 两态);tsc/eslint/prettier/vite build 全绿;渲染 tsc 错误数维持基线 10、PR-5 改动文件零新增类型错误。 注:「热切换真不断流」属运行时功效,需真机验收(本机无 GUI/host-TUN)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 个抗封特性的「类型 + 订阅解析 + 生成 + sing-box check」(运行时抗封效果需真机验收): - ECH(tls.ech.enabled):隐藏 SNI; - Multiplex(multiplex,h2mux/smux/yamux + padding):reality+vision(xtls-rprx-vision) 与 mux 不兼容 → 生成侧 guard 自动跳过; - TLS Fragment(tls.fragment):切分 ClientHello 抗 SNI-DPI。提供全局开关(高级设置,对所有 TLS 节点生效)+ 每节点字段; - httpupgrade 传输(transport.type=httpupgrade,复用 ws 的 path/Host); - Hy2 端口跳跃(server_ports/hop_interval,支持多段逗号分隔范围)。 实现: - 生成统一后处理 applyAntiCensorshipOptions(ECH/每节点 Fragment/Multiplex/Hy2 跳跃),避免散落到 各协议的 tls 构建点;httpupgrade 在 generateTransportConfig;全局 Fragment 在 generateOutbounds 后处理所有 TLS 节点出站。 - 订阅 SubscriptionService 解析以上字段,使订阅节点自动携带(主入口)。 字段确切性:5 项逐一 sing-box check 验证合法字段(确认是 tls.fragment 而非 outbound 级 tls_fragment); 五合一 check(含在 PR-5 selector 内)通过。 UI 覆盖说明:全局 TLS Fragment 开关有完整 UI(纯客户端、非订阅来源);ECH/Multiplex/httpupgrade/ Hy2-跳跃 经订阅自动解析+生成已完整可用,手动节点表单 UI 作为后续补充(用户节点来自订阅)。 验证:tsc/eslint/prettier/JSON 全绿;渲染 tsc 维持基线 10;PR-6 文件零新增类型错误。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HIGH
- [H1] app/custom 分流的「→代理」终止规则补配对 udp443 reject(原 blockQuic 对这些域名失效);
- [H2] 托盘切节点改走 switchMode(原直接 stop+start 硬重启断流);
- [H3] cache_file 会持久化 selector 的 clash_api 选择、重启后覆盖 config default(已实测证实)→
启动后用 clash_api 把 selector 校正回 config.selectedServerId,让 FlowZ 配置成单一真值。
MED
- 节点域名绕过去掉裸 apex:domain_suffix 仅 `.${d}`,防止把共享 apex 下别的真实站点沉降到直连;
- blockProxyQuic 门改判代理可用性(proxyMode!=direct && servers.length>0),不再依赖 selectedServer
解析成功(避免选中失效但 selector 仍出流量时 QUIC 漏过);
- tls.fragment 排除 hy2/tuic/naive:实测 naive 出站拒绝 fragment 字段("fragment is not supported
on naive outbound" → 启动 FATAL,h2/h3 皆然);hy2/tuic 的 TLS 在 QUIC 内、无 TCP ClientHello;
- multiplex 的 vision guard 只看 flow 含 'vision'(去掉 reality 条件——普通 TLS+vision 同样与 mux 不兼容);
- 仅含 server_ports 的 Hy2 端口跳跃节点不再被订阅解析丢弃(从首段范围低位推导 port);
- canHotSwitch 对 servers 按 id 归一化比较(订阅更新仅重排节点不再触发多余重启)。
LOW
- clash_api PUT 加 2s 超时(AbortSignal.timeout);stop() 清 currentIdToTagMap 防对已停 selector 误切。
验证:tsc/eslint/prettier 全绿;渲染 tsc 维持基线 10;naive-fragment、selector、五特性合一 sing-box
check 均通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HIGH
- canHotSwitch 重写为「对齐 selectedServerId + servers 按 id 归一化后整体深比较」:
· 修「切节点 + 改某设置」同时发生时那个设置被静默丢弃——原 hasModeChanged 白名单没覆盖
blockQuic/tlsFragment/dnsConfig/allowLan/bypassLAN/mixedPort/各 TUN 子字段 等;深比较后任何
非 selectedServerId 的差异都退回重启。
· Windows TUN 一律退回重启:route_exclude_address 在 start 时按「选中+appRules」节点构建、
热切换不重生成配置无法补排除集,否则 Wintun 把节点自身出向包回捕进 TUN 成环。
· 移除被取代的死方法 hasModeChanged。
MED
- reassertSelectorSelection 每轮读最新 currentConfig.selectedServerId:避免启动窗口内用户已热切
到别的节点时、reassert 把它 revert 回启动时的旧节点;
- getUniqueTag 预占 proxy-selector/direct/block/direct-loopback 保留 tag:防用户把节点命名为这些
导致 tag 撞车启动 FATAL。
LOW
- 全节点生成失败时提前抛清晰错误(避免空 selector 触发含糊 FATAL);
- naive useHttp3Desc 文案更正:节点自身 QUIC 拨号受保护、浏览器到该节点的 QUIC 仍按 Block QUIC
处理(原文案"不受 Block QUIC 影响"误导)。
验证:tsc/eslint/prettier/JSON 全绿;渲染 tsc 维持基线 10;naive h2/h3 拒绝 fragment 已实测确认
(全 naive 排除正确)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
根因:FlowZ 的 sing-box 编进了 cronet-go 绑定,但运行时靠 purego 从「核心同目录/系统库路径」
加载 libcronet,而 resources/ 未带该库 → 选中 naive 节点必报 "cronet: library not found"、连不上。
与 PR-5 的「全节点 selector」叠加更严重:sing-box 启动会预初始化全部出站,任一 naive 节点缺库即
整体启动 FATAL(连非 naive 节点也用不了)。
打包(与 sing-box 同模式:resources 随 extraResources 的 `**/*` filter 打进安装包):
- scripts/fetch-cronet.mjs:按平台下载 libcronet(linux/win/mac-arm64/mac-x64) → resources/{平台}/,
版本 CRONET_VERSION 应与打包 sing-box 的 cronet-go 对齐(cronet 走 Chromium C API,跨小版本一般兼容);
- package.json 加 fetch:cronet,并接入 release:prepare(打包前先拉库);
- libcronet 体积大、.gitignore 不入库,由 CI/维护者 fetch 后随包打入(区别于较小的 sing-box 直接入库)。
运行时定位:
- ResourceManager 加 getCronetLibFilename / hasCronetLib / ensureCronetBeside;
- ensureWritableCore(Linux 写可写核心) 与 CoreUpdateService(核心单独更新写盘后) 都把 libcronet 拷到
核心同目录——官方 sing-box 包不含 libcronet,naive 始终依赖随 app 打包的那个;mac/win 非便携从
resources 同目录天然可见。
防 brick:缺库时 generateOutbounds 跳过 naive 节点并告警,保证用户仍能用其它协议节点。
验证:tsc/eslint/prettier 全绿;fetch 脚本 node --check 通过。运行时(libcronet 加载、naive 真连通)
需在带库的真机/打包环境验收——本机下载该原生库被安全门拦截、无法端到端实测。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HIGH - [H1] fetch-cronet 去掉 mac 目标:cronet-go 不发 darwin 预编译库(实测最新 release 0 个 dylib), 原脚本两个 mac 目标 404 → process.exit(1) 让 release:prepare 的 && 链中断、所有平台都打不出包。 mac naive 需用 cronet-go build-naive 从源码构建 libcronet.dylib,否则 hasCronetLib()=false 优雅跳过。 - [H2] 被跳过的 naive 节点仍被 app/custom 分流规则按 tag 引用 → sing-box "outbound not found" 启动 FATAL,没真正防住 brick。改为 generateSingBoxConfig 末尾统一把指向「无效出站」的 route.outbound 修正为 proxy-selector(覆盖所有死引用来源)。 MED - [M1] 选中节点是被跳过的 naive → 不再静默切到 nodeTags[0],改为明确抛错(提示选其它协议节点); - [M3] hasCronetLib 改查「sing-box 实际加载目录(dirname getSingBoxPath)」而非内置 resources 目录—— 否则 ensureCronetBeside 拷贝失败时仍误判"可用"、naive 不被跳过 → 找不到库 FATAL; - [M4] 手动替换核心 replaceManualCore 补 ensureCronetBeside,与自动核心更新路径一致; - [M5] fetch 原子写(.tmp + rename)+ 失败删残,避免截断文件被下次当"已存在"误用; - 抽出 isNodeUsable 统一判定(generateOutbounds 跳过 / M1 / H2 共用)。 验证:tsc/eslint/prettier 全绿;fetch node --check 通过;渲染 tsc 维持基线 10。运行时(libcronet 加载、naive 真连通、mac 限制)仍需带库真机验收。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
实测 sing-box 二进制:mac-arm64 含静态 cronet 符号(1219 处)、无 dlopen libcronet.dylib —— cronet 由 CGO 静态编入,naive 开箱即用、无需外部库; mac-x64 二进制未含 cronet(0 处)→ naive 不可用。linux/win 仍走 dlopen 动态库。原 hasCronetLib 在 mac 查 .dylib 必为 false → 误跳 arm64 上本可 用的 naive 节点(甚至选中时抛错)。 - hasCronetLib:darwin 改按 arch 判定(arm64=静态可用,x64=不可用), 不再查 .dylib;linux/win 维持查同目录动态库。 - fetch-cronet.mjs:更正 mac 注释(静态编入、非源码构建),mac 不在下载 范围。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 功能特性列表新增:无缝切换节点、Block QUIC(节点无关)、抗封增强(Fragment/ECH/Multiplex/ httpupgrade/Hy2 端口跳跃)。 - 新增「抗封 / 切换 / NaiveProxy 说明」章节: · 无缝切换默认优雅不断流 + 「切换时中断连接」开关语义; · Block QUIC 行为(节点无关、只禁代理向 udp443、拨号受保护); · 抗封增强各项来源(全局开关 vs 订阅自动识别)+ 排除规则; ·⚠️ NaiveProxy 需 libcronet、打包方式、以及 macOS 无官方预编译库的限制(缺库时 naive 节点 自动跳过)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
解决"核心更新后配置解析不支持导致 brick"的健壮性问题: - 预检(替换前):新核心二进制可执行 + 用「针对新版本生成的」FlowZ 配置跑 sing-box check;不通过则不动现役核心、代理继续运行,并标记问题版本。 - 自动回滚:更新后新核心首次启动失败(proxy 'error')→ 回滚到备份核心并以 旧核心重启;标记问题版本防再次自动安装。 - 问题版本跳过:预检/启动失败的版本记入 known-bad,checkUpdate 自动跳过 (手动更新可强制安装)。 - 备份生命周期:更新后保留旧核心备份作回滚网,新核心「首次成功运行」 (recordSuccessfulVersion)即删除备份——稳定即不再回滚、省磁盘。 - 兼容版本带闸门:默认仅自动更新到配置生成器已验证的 minor 带(≤1.13);跨 minor(如 1.14)不自动更、提示随 App 升级。设置可关(手动更新不受限)。 实现:CoreUpdateService(预检/known-bad/备份/自动回滚/带闸门)+ ProxyManager .buildPreflightConfigJson(按目标版本生成预检配置,复用 coreVersion 分叉)+ index 'error' 钩子 + UserConfig.restrictCoreUpdateToCompatibleMinor 开关 (设置 UI + 中英 i18n)。 实测:sing-box check 正确接受 1.13.x 全特性配置、拒绝跨版本不兼容字段 (inbound sniff);build:main / vite build / tsc / eslint / prettier 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #1 版本带闸门 parseFloat 误判(HIGH):改整数编码 major*1000+minor 比较, 修 "1.100"→1.1 漏放、"1.9"→1.9 误判等多位 minor 塌缩问题。 - #2 自动回滚被自动重启风暴延迟(HIGH):待验证窗口内 ProxyManager 抑制自动 重启(setAutoRestartSuppressed),新核心首次异常退出立即上报 error → 立即 回滚,而非在已知坏核心上空转 MAX_RESTART_COUNT 次后才回滚。 - #3 1s 假成功删备份致 brick(HIGH):'started' 仅代表存活 1s,不再立即删 备份;改启动 30s 稳定观察期,期内无 error 才删;期内崩溃→回滚(备份仍在)。 - #4 陈旧 pending 误回滚(MED):pending 加更新时间戳,超 5 分钟未成功运行的 error 不再触发回滚(视为与本次更新无关,如用户期间改了配置)。 - #7 问题版本永不解除(LOW):版本成功运行后从 known-bad 移除,误标记可恢复。 - #9 exec 注入面(LOW):预检改 execFile(无 shell),消除引号转义类问题。 - 附带(review 外自查):预检在备份之前,原 catch 的 restoreBackup 可能误恢复 陈旧 .bak 致静默降级 → 加 backupMade 守卫,仅本次确已备份才恢复。 tsc/eslint/prettier 全绿;encodeMajorMinor 各边界(1.13/1.14/1.20/1.100/1.9/ 2.0)实测判定正确。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
三核统一到官方 1.13.13(with_naive_outbound),消除版本漂移 (原 linux 1.13.3 / win 1.13.6 / mac-arm64 1.13.11)。mac-x64 按当前 编译保持 1.12.13(FlowZ 仅出 arm64 包;Intel naive 暂不纳入)。 libcronet:fetch-cronet 钉的 v148.0.7778.96-1 实测与 1.13.13 内置 cronet(cronet-go ade33496)字节一致(sha256 dc7293a9…),ABI 匹配, 不需改 fetch-cronet。 实测验证: - sing-box check 通过 FlowZ 1.13.x 全特性配置(selector / naive / hy2 跳端口 / tls.ech / tls.fragment / multiplex / httpupgrade / block 出站 / route action:sniff)。 - naive 出站运行时 dlopen cronet 正常(成功初始化引擎并拨号)。 - 1.13.13 按预期拒绝 inbound sniff(removed in 1.13.0),FlowZ coreVersion<1.13 分支 guard 正确,升级后仍生成 1.13.x 风格配置。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
紧接前一提交的三核统一,把 mac-x64 也升到官方 1.13.13 darwin-amd64 (with_naive_outbound + lib/darwin_amd64 静态 cronet),不再留 1.12.13。 - resources/mac-x64/sing-box → 1.13.13(静态 cronet,Intel mac naive 可用) - electron-builder.json:mac target arch 加 x64 → arm64/x64 一并打包发布 - ResourceManager.hasCronetLib:darwin 改为恒 true(两 arch 核心均静态含 cronet,naive 皆可用),移除原 arch-gate - ProxyManager:更新 1.12.x sniff 分叉注释(打包核心现全 ≥1.13.13,该分支 仅为向后兼容旧 userData 核心保留) 至此四平台核心统一 1.13.13;naive 在 mac 两 arch + linux + win 全可用。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
人工配置开放 ECH/Multiplex 的渲染层共享组件。各表单 schema/默认值/submit 仍各自维护 (RHF 限制),渲染统一走此组件避免多表单重复 JSX。MultiplexFields 内置 disabled+提示 (vision flow 不兼容时由调用方传入)。后端 applyAntiCensorshipOptions 已消费对应字段, 本组件只补人工入口。 [WIP] 待接入 vless/trojan/vmess/ss/tuic/hysteria2/anytls 表单 + i18n。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
把原来仅订阅自动识别的抗封字段开放到各协议人工表单(additive,零后端改动—— ProxyManager.applyAntiCensorshipOptions 已消费这些 ServerConfig 字段): - ECH:vless/trojan/vmess/tuic/hysteria2/anytls(TLS 段下方开关) - Multiplex(协议 + max/min + padding):vless/trojan/vmess/ss;vless 按 flow=xtls-rprx-vision 禁用+提示(镜像后端 vision guard) - httpupgrade 传输:vless/trojan/vmess(network 枚举 + 复用 ws path/host) - Hysteria2 端口跳跃:serverPorts(逗号分隔范围) + hopInterval 新增字段即共享:shared/anti-censor-fields.tsx(EchField/MultiplexFields)避免 多表单重复 JSX;各表单 schema/默认值/submit 仍各自维护(RHF 限制)。i18n 中英 各补 15 键。协议级限制靠"只接支持的表单",flow 级靠组件 disabled+提示。 vite build / eslint / prettier / JSON 双语 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 个协议表单的 TLS/传输/Reality/基础字段渲染存在大量重复 JSX。本次抽出 4 组
共享渲染组件(schema/默认值/submit 仍各表单自持,仅渲染共享),9 表单接入,净删
约 780 行。
新增组件:
- shared/basic-fields.tsx: AddressField / PortField(placeholder 必传)
- shared/tls-fields.tsx: TlsServerNameField(label/desc/placeholder/optional 可配) /
FingerprintField / AllowInsecureField / AlpnField(placeholder 必传)
- shared/transport-fields.tsx: WsPathField / WsHostField
- shared/reality-fields.tsx: RealityPublicKeyField / RealityShortIdField
接入:vless/trojan/vmess/ss/hysteria2/tuic/anytls/http/socks。
豁免内联:naive(全表 text-foreground 标签主题,分片抽离会造成视觉不一致)、
ssh(独有 grid-cols-3 + col-span-2 布局与端口 ||22 兜底)。
顺手规范化的一致性修正(逐条):
1. vless Reality 指纹:硬编码中文 → i18n FingerprintField
2. vmess TLS 指纹:补回 none 选项
3. anytls TLS 指纹:补 none + i18n placeholder
4. anytls Reality 指纹:补 none + i18n placeholder + desc
5. anytls Reality shortId:补 shortIdDesc
6. anytls port:补 portDesc
7. http port:补 portDesc(动态 443/80 placeholder 经 prop 保留)
8. socks port:补 portDesc
9. anytls/http SNI:字面量 "SNI (...)" → t('servers.sni')+optional
network 下拉因各协议 enum 大小写不同仍内联。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
主线(根因):配置生成层用 parseFloat("1."+minor) 判断版本带,把 minor 当小数
——"1.9"→1.9 被误判 >1.13、"1.20"→1.2 被误判 <1.13。而核心更新层早已用整数编码避坑。
本次把两边统一到单一权威 src/main/utils/version.ts。
1. 版本解析统一(主线)
- 新增 utils/version.ts:encodeMajorMinor(major*1000+minor,容忍 v 前缀/后缀)+
coreVersionAtLeast(未知→现代,匹配打包核 ≥1.13.13 与 getCoreVersion 的 1.13.0 兜底)。
- ProxyManager 6 处版本门(含一处复用 versionNum 的下游站点)改用 coreVersionAtLeast。
- CoreUpdateService 删私有 encodeMajorMinor,改用共享版(消除双实现)。
- 真实版本映射完全一致,额外修正 1.9/1.20 误判;6 站点统一未知→现代默认(经
caller-trace 确认 coreVersion 在生成时恒为真实版本,旧 legacy 兜底为死路径)。
2. ws early-data 落配置:generateTransportConfig 补 emit max_early_data /
early_data_header_name(订阅/分享链已解析存入 wsSettings,此前未落运行时配置致静默失效)。
3. 下载完整性校验:downloadFile 累计字节与 Content-Length 比对,截断下载经 handleError
自动换镜像重试一次,拦截不完整包流入 extractCore。
4. naive 漂移提示:核心更新后若存在 naive 节点,提示 naive 依赖随 app 打包的 libcronet、
跨版本更新可能 ABI 漂移(该漂移运行时 dlopen 才暴露,sing-box check 预检无法发现)。
build:main + eslint 通过;独立 review 全绿(语义保形经 caller-trace 确认)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
独立 review 发现的 P1 项(栈在 P0 之上)。 1. ProxyManager.canHotSwitch:JSON.stringify 深比较改为递归按 key 排序的 stableStringify, 渲染层重建配置对象键序不同不再误判"配置已变"而退回整重启、废掉热切换。数组顺序保留 (customRules/appRules 顺序具语义);丢弃 undefined 键,与 JSON.stringify 语义一致。 2. CoreUpdateService.compareVersions:容忍前导 v 与 -prerelease/+build 后缀、每段 NaN→0, 修 "v1.13.13"/"1.13.13-beta" 这类标签的误比较。 3. 核心更新抑制闩兜底计时器:更新后若迟迟无 'started'/'error' 解决待验证态(如更新时代理 未运行、用户不重启),PENDING_MAX_AGE_MS 到期清待验证 + 解除自动重启抑制,避免抑制闩 永久挂起。在成功稳定/回滚路径中清除该计时器(单飞 updateCore 保证不串台)。 4. 订阅 SSRF 兜底:fetchSubscription 限 http(s) 协议、拒指向本机/内网/link-local/CGNAT 的 字面 IP 主机(拦 file://、169.254.169.254 云元数据、回环/内网)。fc/fd 判定加冒号闸门, 不误伤 fcm.googleapis.com 等公网主机名。基于主机名的 DNS 旁路仍存在(字面 IP 兜底)。 5. 订阅空结果区分:0 节点时区分"格式不识别"与"节点 URL 都解析失败",不再静默冒充成功。 6. JSON 订阅 ws transport.host 折叠进 Host header(与 httpupgrade 对齐),修同一节点经 JSON 订阅 vs 分享链解析出不一致 wsSettings(丢 Host)。 build:main + eslint 通过;独立 review 收口(2 处发现——fc/fd 主机名误拦、undefined↔null 误判——已修复并验证)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
① 打磨 - 删 5 处残留 console.log(trojan/hysteria2 表单,避免把 config 形状泄到 devtools) - 删孤儿 i18n key serverPorts/serverPortsDesc(无 t() 引用) - vmess 校验消息本地化:schema 改 createVmessSchema(t) 工厂,硬编码英文→t();补全 缺失的 servers.addressRequired key(多表单引用但此前缺失,原渲染裸 key) - anti-censor-fields 的 t(key,'中文兜底') 去掉第二参(key 两 locale 均存在,避免 英文环境兜底成中文的反转契约) - PortField 空输入→undefined(可清空/重输,zod 提示必填),不再静默吞成 0 ③.9 抽共享 zod 片段 - 新增 shared/field-schemas.ts:echSchemaShape/multiplexSchemaShape(zod 形状)、 echDefaults/multiplexDefaults、readEchDefault/readMultiplexDefaults(加载分支)、 buildMultiplexSettings(submit,vless 经 skipVisionFlow 带 vision guard) - mux 4 表单(vless/trojan/vmess/ss) + ech 6 表单(含 hysteria2/tuic/anytls) 改用 spread/ helper,吃掉组件化后残留的 schema/默认值/submit 重复。行为完全一致(字段名/校验/ 默认值/提交形状不变),经 codegraph 波及面核查无漏表单。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… manifest ②.6 hasCronetLib 复制失败误报:ensureCronetBeside 复制失败改为记录(不再静默吞); 新增 getCronetLibStatus 区分 available/copy-failed/no-lib——linux/win 目标库缺失时 回查打包源库,存在则判 copy-failed(拷贝失败)而非 no-lib;naive 不可用文案按平台 与成因分流(macOS 专属文案仅 darwin),不再对 linux/win 误报"macOS 无预编译库"。 ②.7 packet_encoding 可配:ServerConfig 加可选 packetEncoding;generateProxyOutbound vless/vmess 用 server.packetEncoding ?? 'xudp'(空串则省略字段);订阅解析读 ob.packet_encoding。默认 xudp 不变,给 xudp 不兼容的服务端一个逃生口。 ②.8 route 去 as-any + 去重:移除全部 7 处 route-rule push 的 as any(SingBoxRouteRule 类型已覆盖);udp443-reject 字面量、DoH 域名列表、isIpv4/isIpv6 分类器上提为 模块级常量/工厂,route 与 DNS 生成共用,保持字节一致、行为不变。 ③.10 版本耦合 manifest:新增 src/shared/core-manifest.json(bundledCoreVersion/ compatibleCeiling/cronetVersion 单一来源),CoreUpdateService(COMPATIBLE_CEILING)、 ProxyManager.getCoreVersion(兜底版本→1.13.13)、fetch-cronet.mjs(CRONET_VERSION) 共读, 核心升级改一处即可。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A5 改 PortField 空输入→undefined 后,受控 input 的 value 会变 undefined 触发 React 「受控→非受控」warning(清空端口时)。补 value 兜底为 '',与 mux 数值字段一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sway-Chan
added a commit
that referenced
this pull request
Jun 21, 2026
…图标焕新 (#135) * fix(tailscale/ui): 登录态根治(always-emit) + 拓扑订阅跟随 started + 卡片名折叠 ① Tailscale 登录态根治:buildOutbounds always-emit 有持久会话(authKey 或 state 目录非空)的 TS 节点→常驻主核、 登录态由 STATUS 流持续报告(与是否当前出口无关,修「非出口已登录节点恒显需登录」),且已在 selector 里可热切换不重启。 loggedIn 真值仍只由 STATUS(Running/Starting && !expired)给,stateExists 仅决定是否运行→不重蹈 #132 误判。 detectTailscaleAuthUrl 仅对当前出口弹登录 toast,防 always-emit 后多组网节点登录 URL 刷屏。 ③ 拓扑自动刷新:StatsService 连接流订阅改为跟随 started(代理运行即订阅),不再依赖渲染端 watcher 计数到达时机 (挂载期 watch 与 started 时序竞态曾致「启动后拓扑不刷新、需先点连接信息」);广播仍由窗口可见性门控省开销; remove/resetConnectionsWatcher 不再退订(退订只在 stop);connection-topology 回退 [proxyRunning] dep。 + stats-service-gating 测试随新行为更新。 ④ 卡片名折叠:ServerActions 不可测节点不再渲染「不适用」文本占位(禁用 ⚡ 的 title tooltip 已表意),名称优先不截断。 * fix: stats订阅竞态(api-client-ready) + 切节点出口陈旧(清旧值) + DNS T1/T2 + dashboard撤无效参数 真机回归根治 + 此前未提交 #47-56 批量检查点。 - symptom-1 stats全0: 订阅移到 'api-client-ready'(client就绪后; emit('started')时client未建→早退) - symptom-2 出口陈旧: markProxyConnecting清旧proxy值 + node-hot-switched调它(覆盖热切换+重启两路径) - T1 dns_mode文档化注释(零行为) / T2 winLoopRisk解耦takeoverSystemDns(Win真机nslookup待验) - #56 activeConnections改connMap.size; dashboard撤无效URL参数(bundled面板只读localStorage) * fix(i18n): TS 登录角标「登录」→「未登录」(避免误读为已完成登录) * feat: Tailscale 单节点硬限(UI 拦截 + 配置兜底归一) 同设备所有 Tailscale 账号共用同一段 tailnet 地址(100.64.0.0/10),多个会互相 顶掉,故全局只许一个。仅限 tailscale,不触碰 WARP/WireGuard/其它协议。 - shared/endpoint-routes: 新增纯函数 tailscaleSlotTaken(servers, editingId?), editingId 排除自身(编辑现有 TS 放行);UI 拦截 + 主侧兜底共用,可离线单测。 - use-server-actions: saveServer(新增非编辑)+ cloneServer(克隆恒新增)两处过闸, 撞限 toast.error(servers.tailscaleSingleOnly) 并 return,不调 build*Server。 - ConfigManager.validateConfig: 兜底归一已是非法多 TS 状态(旁路 save/损坏导入), 保留第一个丢弃其余 + warn,沿用 sanitize 不 throw(不连累整份配置回落默认)。 - i18n: 新 key servers.tailscaleSingleOnly,5 locale parity(含 fa-IR RTL)。 - 单测: tailscaleSlotTaken 覆盖空槽/占用/编辑放行/异协议/大小写/多 WG 放行。 * feat(servers): 测速结果移卡片右下角 + 组网节点内网IP/路由 info popover #59 测速结果(延迟/超时/N/A)从节点卡名称行的操作按钮组抽出为独立 SpeedBadge,移到卡片右下角(近底部「传输:/加密:」行);名称行不再被结果 挤折叠。网格卡与列表行两视图位置对齐。 #61 组网类型节点(wireguard/tailscale,WARP 本质 wireguard)在「传输:」前 加 ⓘ,hover/click 弹内网IP + 路由: - 内网IP:Tailscale 取 api STATUS 流已携带的 tailnet IP(self.tailscaleIPs, 主进程 emitTailscaleStatus 早已下发、渲染端此前未接出)→ 新增 store tailscaleIps + setTailscaleIps,handleTailscaleStatus 写入;WireGuard 取 wireguardSettings.localAddress。 - 路由:Tailscale = accept/advertise routes;WireGuard = peer.allowed_ips。 非组网节点(vless 等)不显此图标。复用既有 HoverCard(同 InfoTooltip 范式)。 i18n 新增 4 key(servers.meshInfo*)5 locale 满 parity(含 fa-IR)。 * fix: WARP 测速超时(#58 WG endpoint 域名 server 补 domain_resolver) + App 自更新应用 ghProxyPrefix(#60) #58:sing-box 1.14 起 dial fields(含 endpoint)用域名 server 时需显式 domain_resolver(实证: endpoint 顶层 domain_resolver check 合法、peer 内则 FATAL unknown field;缺 resolver 触发 "missing domain_resolver in dial fields will be removed in 1.14" 路径)。buildWireGuardEndpoint 原不下发任何 domain_resolver,致 WARP(engage.cloudflareclient.com 域名)测速拨号无确定解析上游 → 超时;Dalutone(IP server)无需 DNS 解析故正常。修:buildWireGuardEndpoint 加可选 domainResolverTag, 域名 server 时在 endpoint 顶层 emit domain_resolver(IP 字面量跳过);测速路径传 'dns-direct' (对齐同函数 buildProxyOutbound 分支),config-gen 路径传 getNodeResolverTag(config,'dial') (对齐普通协议)。SingBoxEndpoint 加 domain_resolver 字段。补 4 个纯逻辑单测 + 更新 config-snapshot。 #60:UpdateService 自更新下载兜底镜像调 ghMirrorUrl(url) 不传 ghProxyPrefix,国内用户配了 gh 加速 也只回落内置 preset[0]。对齐 CoreUpdateService/CoreDownloader:UpdateService 加 configProvider + setConfigProvider(index.ts 注入 configManager.loadConfig),downloadWithHardening 先解析用户 ghProxyPrefix,:783 兜底镜像改 ghMirrorUrl(url, ghPrefix)。补 resolveGhPrefix 纯逻辑单测。 * feat(dashboard): 内置面板随包 + 应用内窗口一键直连 + 复制连接信息 (#55) 随包内置 sing-box 官方面板(scripts/fetch-dashboard.mjs 拉 gh-pages 构建到 resources/dashboard, electron-builder extraResources 三平台打入),核以 dashboard:{enabled,path} serve 本地目录——打开即时、 离线可用、根治首次联网慢启动;运行时下载覆盖目录(<userData>/dashboard)优先,皆无则回落联网下载兜底 (ResourceManager.resolveDashboardServeDir)。 OPEN_SINGBOX_DASHBOARD 改开应用内 BrowserWindow 加载 /dashboard/,经专用 preload 在 document-start 预写 localStorage(权威键 sing-box-dashboard.servers + 旧版迁移键 sing-box-dashboard.server)实现一键 直连免手填后端;窗口加导航闸(setWindowOpenHandler 拒子窗口/外链走系统浏览器 + will-navigate 锁本地源) 防第三方面板代码跨源导航泄漏 secret。新增 GET_SINGBOX_DASHBOARD_CONNECTION + 设置页「复制连接信息」按钮 (URL+secret 经 IPC 现取,secret 不长驻渲染端 store)。i18n 5 locale 同步新 key。 * fix: 出口混乱(reassert移client创建后) + 不可测节点隐藏⚡/角标「不支持测速」 - reassertSelectorSelection 从 client 创建前(812)移到创建后: 原 if(!client)break 首启 client 未就绪时直接放弃→cache_file 旧节点残留→「选X实际走上次出口」(首次打开才现、二次正常)。与 stats 同类「client 未就绪」bug,修 stats 时漏了它。 - 不可测节点(TS/custom-endpoint/reverseMesh): ⚡按钮隐藏(不再 disabled 占位致歧义) + 角标「不适用」→「不支持测速」(5 locale parity) * fix(dashboard): 内窗口 preload 路径上跳两级(修自动连空表单回归) helper-handlers.ts 在 dist/main/main/ipc/handlers/、dashboard-preload.js 在 dist/main/main/, 原 path.join(__dirname,'dashboard-preload.js') 解析到不存在的 ipc/handlers/dashboard-preload.js → preload 静默不加载 → localStorage(sing-box-dashboard.servers/.server) 未预写 → 面板回落空表单。 上跳两级锚定。asar 内真实层级 + 主窗口 preload(index.ts 同级故 work)对照实证。 * 移除设置·高级「远程实例」段并在面板段显示面板 URL 任务1:dashboard 原生支持多后端(面板内「添加后端」填 URL+secret 即可管理任意远端实例), 设置里独立的「远程实例」段是重复 surface,整体移除:删除渲染段组件 + IPC 通道 (OPEN_REMOTE_DASHBOARD/TEST_REMOTE_INSTANCE)+ 主进程 handlers + api-client 方法 + config-handlers 的 strip/merge secret + ConfigManager sanitize + types(RemoteInstance/ remoteInstances/activeInstanceId)+ 5 locale i18n key + 专属测试。SingBoxApiClient 共用 本地管理 API,保留不动(注释去除已删特性语境)。 任务2:面板段显示面板 URL(http://127.0.0.1:<动态端口>/dashboard/),只读、点击经 shell.openExternal 在浏览器打开。复用 GET_SINGBOX_DASHBOARD_CONNECTION 的 url 字段 (不含 secret,安全可显),running + 面板开关 on 时拉一次,否则显「代理未运行」。 * fix(dashboard): 内窗口面板首开按 FlowZ 系统 locale 对齐语言 Electron 窗口 navigator.languages 常为 en → 面板默认 auto 自检显英文(中文系统也英文)。 preload 首开(用户未手选过)按 app.getLocale() 映射到面板合法码(en/zh-Hans/zh-Hant/fa/ru) 写 sing-box-dashboard.language;已存在跳过不覆盖手选。源码实证 xl 选项码 + El() 映射逻辑。 * fix(speedtest): WARP 测速默认端点改 cp.cloudflare + 7 项 simplify 清理 WARP 测速超时修复(Mac 真机坐实): 默认测速 URL 从 www.gstatic.com/generate_204 改为 cp.cloudflare.com/generate_204。 根因:gstatic 有国内 CDN 镜像,AliDNS(223.5.5.5) 等国内 DNS 解析到国内镜像 IP, WARP 等海外全隧道出口连不上该国内 IP 致测速恒超时;cp.cloudflare 全球任播无镜像, 对 WARP/海外/国内出口一致可达。同步更新 speed-test 默认值相关断言。 simplify(去重后 7 项): 1. server-card/server-row 双调 tailscaleStatusChecking → 提局部 isChecking 2. helper-handlers 面板 locale 映射 4 层三元 → 抽纯函数 mapElectronLocaleToDashboardLang(含单测) 3. mesh-info-popover/server-page 内联 ==='tailscale' → 复用 isAccountBasedProtocol 4. ResourceManager 抽私有 hasIndex,hasBundledDashboard/resolveDashboardServeDir 共用并展平 5. helper-handlers 面板 preload 路径换稳锚点 app.getAppPath()+dist/main/main/dashboard-preload.js 6. MeshInfoPopover 整表订阅 tailscaleIps → 精确订阅 [server.id](模块级 EMPTY 兜底保引用稳定) 7. app-store setTailscaleIps 加等值短路,对齐 setTailscaleAuthUrl/setTailscaleStatusProbing * feat: 应用图标焕新 + README 重写(对齐 1.14/组网/Conduit·刷新截图) 图标:全新 FlowZ 字形图标(母版 ui/icon.svg)→ build/icon.{png,icns,ico};托盘两态 resources/app.png(已连接·彩色)/app-gray.png(未连接·灰)。 README:现代 hero banner+徽章;内容对齐 sing-box 1.14(原生 gRPC 管理 API/官方 dashboard)、组网(WireGuard/WARP/Tailscale)、自定义换核(Snell)、Conduit 设计系统、5 语言;修正 Shadow-TLS(传输插件非协议)/规则条件数(15)/license 链接;界面预览刷新为 Conduit 新 UI(9 张)+移除过时 helper 截图。 * fix(dashboard): 修面板地址误显未运行 + 内窗口面板语言 + 繁简误判等 5 项 1. 面板地址恒显「代理未运行」(真bug):GET_SINGBOX_DASHBOARD_CONNECTION handler 漏返 url 字段,渲染端 info.url 恒 undefined → 地址回落未运行文案。 handler 补 url: info.url,typed 返回形状加 url。 2. 内窗口面板语言仍英文 (真bug):app.getLocale() 拿的是 Electron app bundle locale(FlowZ.app 未声明 zh 恒 en),非 UI 语言。改由渲染端 i18n.language 经 IPC 透传,handler 用 mapElectronLocaleToDashboardLang(locale || app.getLocale()), app.getLocale 仅兜底。IPC 链:section → api-client(locale?) → preload → handler。 3. dashboard-locale 繁简误判 (High):旧 /^zh.*(hant|tw|hk|mo)/ 把 mo/tw/hk 当裸 子串 → zh-Hans-MO、zh-hans-x-promo 误判繁体。改脚本优先(hans→简)+地区码锚定 段边界 (^|-)(tw|hk|mo)(-|$)→繁。补单测覆盖 5 例。 4. server-page 探针 useEffect (Low):补 return ()=>clearProbeTimeout() 清未 fire 的 13s 兜底 timer(卸载后不再误 fire);指纹由仅数量改为排序 server id 拼接, 等数量替换也能重探。 5. setTailscaleIps 去重 (Low):join(' ') 改 length + 逐元素比较,不依赖分隔符 不出现在元素值的隐含前提。 * fix(runtime): 修复 TS 首连出口探测/远程实例明文残留/flush 时序三项运行期问题 1. TS 首连出口探测(事件驱动 re-probe + 有界退避兜底) - IpInfoService 新增 refreshProxyPostConnect:首连专用更宽退避(4 轮 ×4s), 全程 loading=true 转圈,重试耗尽才落 error;常规 refreshProxy/手动刷新/切节点 仍用原预算(maxAttempts),互不拖累。index.ts 'started' 后的 1.5s 首探改走此路径。 - ProxyManager.handleTailscaleStatus:选中的账号制(TS)节点 STATUS 翻 Running(隧道就绪) 上升沿(lastTsSelectedRunningId 去重)发 'tailscale-selected-running',index.ts 监听后 立即 refreshProxy(),隧道一就绪即出真出口、不等满退避。 2. B 隐私回归:清除 CONFIG_GET 下发的远程实例明文 secret 残留 - ConfigManager.validateConfig 彻底 delete 已移除 feature 的死字段 remoteInstances/activeInstanceId(loadConfig 与 saveConfig 两路径都过):既清盘 又不再随 CONFIG_GET 内联回退下发渲染端,恢复「远程 secret 明文永不出主进程」不变量。 3. E flush/reassert 时序:关出口混乱窄窗 - startInternal 管理 API 路径把连接 flush 链到 reassertSelectorSelection().finally(), 使 flush 的 CloseAllConnections 在 selector 已校正回 config 后才发,不再按 cache_file 旧 selector 重连致出口混乱;无管理 API(<1.14)路径保持直接 scheduleConnectionFlush。 reassert 仍 best-effort 不阻塞 start,flush 绝不丢。 补 IpInfoService 首连退避单测 + ConfigManager 死字段清除单测。 * fix(runtime): 修复 TS 节点 stop 后重连丢出口 re-probe + 热切换到 TS 闪暂不可用 - ProxyManager.startInternal 起点重置 lastTsSelectedRunningId:stop() 断 tailscaleApiClient 后无 STATUS 帧来清此去重标记,残留 Running 标记会使重连 同一 TS 节点时新首帧 Running 被去重命中、tailscale-selected-running 不发射 → 事件驱动出口 re-probe 丢失。会话起点显式归零,每次新会话从干净状态开始。 - node-hot-switched 携带 accountBased payload:切到账号制(TS)节点时隧道 (DERP/peer 握手) 未就绪即耗尽常规预算会闪「代理出口暂不可用」→ 改走宽退避 refreshProxyPostConnect(与 started 首连同治);IP 类节点保持常规 refreshProxy。 - 新增 proxy-manager-ts-selected-running 单测(4 例):上升沿去重 / 运行期掉线 重连再 emit / stop 后残留标记 + startInternal 起点归零 + 重连同节点再 emit。 * 重制 macOS 托盘两态图标为透明背景纯字形 去掉完整 app 图标的白色圆角 tile 背景,托盘只渲染 FlowZ Z-flow 字形(traces + monogram + highlight),透明 alpha 背景,深浅菜单栏均可见。 viewBox 收紧到字形包围盒使其放大铺满画布、无预留边框。 两态区分明确:app.png 用原 teal-blue 渐变表连接中、app-gray.png 去饱和为中性灰表未连接。traces 端点改空心环,去掉阴影。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
独立 review 第三轮(P2 打磨)。两路并行 review 全绿(渲染 zod 行为保形逐表单核验、主进程 route 去重字节一致)。
渲染(① + ③.9)
shared/field-schemas.ts:抽 ech/mux 的共享 zod 片段 + 默认值 + submit helper,mux 4 表单 + ech 6 表单接入,吃掉组件化后残留重复(行为逐表单核验一致,codegraph 波及面无漏)主进程(② + ③.10)
hasCronetLib区分「库缺失 vs 拷贝失败」,naive 不可用文案按平台/成因分流(不再对 linux/win 误报「macOS 无库」)packet_encoding可配(默认 xudp,空串省略,订阅可携带)——给 xudp 不兼容服务端逃生口as any+ udp443/DoH/IP 分类器去重(字节一致)core-manifest.json单一来源(核心升级改一处)验证
build:main+build:renderer+ eslint 全绿;两路独立 review 全绿(一处 LOW:PortField 受控 value 兜底,已修)。🤖 Generated with Claude Code