Commit Graph

808 Commits

Author SHA1 Message Date
wangjia a392d18a6a feat(web): iOS/iPad 下载卡合一 + 双按钮 + 正式版「?」气泡提示
- 三张 iOS/iPad 卡合成一张:测试版(TestFlight,实心可点)+ 正式版(App Store,描边禁用)双按钮。
- 「?」移入正式版按钮内,自定义 tooltip 气泡(走设计 token,上浮淡入+小箭头)替代原生 title,
  悬浮/聚焦显示「美区即将上线 · 需美区 Apple ID」;光标不再变问号。
- 新增 i18n 文案 dl.us_soon(6 语)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 16:33:16 +08:00
wangjia a913fe56fb feat(web): 下载区补 iPad + iOS 分测试版/正式版 + 需美区ID提示
按需求调整官网下载卡片:
- 补 iPad 卡片(tablet 图标,universal app→同 TestFlight 公测链接)。
- iOS/iPad 明确分「测试版」(TestFlight,可点)与「正式版」(App Store,
  敬请期待占位)——加 badge 标注(dl.beta/dl.stable i18n,6 语言)。
- 测试版卡加「?」悬浮提示「需美区 Apple ID」(dl.us_id i18n)。
- badge/「?」样式全走设计 token(--accent-subtle/--accent/--bg-subtle/
  --fg3),不硬编码颜色。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 09:02:43 +08:00
wangjia 18fb5177be feat(web/client): iOS 下载/更新指向 TestFlight 公测链接
公测公开链接 https://testflight.apple.com/join/6HFfw8Jc。
- 官网 Download:site.ts 加 downloads.ios + Download.astro iOS 按钮启用
  (原为禁用占位),点击直跳 TestFlight;ver 顺带改 iOS 15+(对齐最低版本)。
- 客户端自更新:清单模板 deploy/single-node/version.yaml 的
  download_urls.ios 从空填成 TF 链接 → 客户端「有更新」按钮直跳 TestFlight。
- pangolin1 现行 /etc/pangolin/version.yaml 已同步改(/version 已反映),
  现有用户即时生效。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 08:55:49 +08:00
wangjia cd1ef98b27 fix(ci): checkout 浅克隆 + sing-box clone 重试(外网走 frps 稳定性 A)
外网 runner 的 git fetch 经 ali frps 隧道偶发超时(run#289/290 的 Checkout
卡死即此)。A 层加固:
- deploy-client.yml 全部 actions/checkout 加 fetch-depth:1 —— 从拉全历史
  降到一层,数据骤减,直接打掉 Checkout 步超时根因(外网/在家都受益)。
- build-libbox.sh sing-box clone(已 --depth 1)加 3 次重试 —— 另一大 fetch
  抖了自动重来。
checkout 步显式重试(需替换 actions/checkout)暂缓,先看浅克隆后失败率;
仍高再上 checkout 重试 + frps 隧道调优(B)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-08 12:33:17 +08:00
wangjia 6a3cb48a02 fix(ios): 最低部署目标 14.0→15.0(消除 ASC 警告 90068)
TestFlight 上传报 ITMS-90068 MinimumOSVersion too low:app 最低 iOS 14.0,
Apple 要求 2027 春起 ≥15.0 才能上传/分发。改 IPHONEOS_DEPLOYMENT_TARGET
(pbxproj 6 处:Runner+PacketTunnel 扩展 × debug/release/profile)+ Podfile
platform :ios 14.0→15.0。iOS15(2021)覆盖面足够,放弃 iOS14 无影响。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-08 11:18:42 +08:00
wangjia 6c6d9636bb fix(ci): 串行化 mac 三 job(android→macos→ios) 消除单 runner 争用
run#286 暴露:三个 runs-on:mac 并行,家里只一个 mac runner(capacity=1),
android 占住后 macos/ios 抢不到 runner 被整体 cancelled(全步骤含 Checkout
都 cancelled)→ iOS 没跑起来、没上 TestFlight。用 needs 把 mac 三 job 串成
链(build-macos needs android、build-ios needs macos)+ if:!cancelled() 保证
上游失败也照跑,一个跑完再跑下一个,不再争用。windows/deploy-web 在各自
runner 上仍并行。对齐 jiu 的 needs 链。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-08 06:59:33 +08:00
wangjia 8f0c2ef81b ci(client): web 并入 deploy-client(一个 client-v* 发全平台) + iOS 加密合规键
1) deploy-client.yml 加 deploy-web job(usercenter+website→CF Pages,与
   deploy-site.yml 同源共用 scripts/ci/*.sh),release-deploy needs 带上它:
   一个 client-v* tag 即发 web + iOS/mac/Android/Windows(对齐 jiu)。
   site-v*/deploy-site.yml 保留供 web-only 单发。清理过时 runner TODO 注释
   (mac/windows 用户级 runner 今日已就绪)。
2) iOS Info.plist 加 ITSAppUsesNonExemptEncryption=false(仅标准加密,免
   ASC 每次上传加密合规问答;法务判定需申报再改 true)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-08 06:13:08 +08:00
wangjia d094a53d7e docs(ci): W3/W4 落地记录——git 局域网直达 24ms + 华为防私接 DHCP 偏差
W3/W4 标记完成。W4 按计划(DSM 443 反代改 HTTPS+导入 ali LE 证书)。
W3 改方案:原定 NAS 接管 DHCP 被华为 Q2S 防私接 DHCP 挡死→放弃,改成
NAS 装 Synology DNS Server(git/nas/win 单主机主区+转发其余)+仅 runner mac
家里 Wi-Fi 手动 DNS=.200。§8 记完整过程/偏差/坑(mDNSResponder 负缓存需 flush)。
真相源 baize domains.yaml 家内分光 note;NAS 運維归 ~/code/nas。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 22:19:19 +08:00
wangjia c152c2aadf docs(ci): 标记 W5 已完成(runner 收敛为单一用户级+relay 退役)
W5 执行记录:mac-pangolin-2(id=7)本就用户级+已指向域名,唯一 bug 是
label 错成 nas(根因 Shadowrocket 掐连接致 Declare 从没上报成)。换
pangolin 后改 config labels nas:host→mac:host + kickstart 重启,
Declare 成功、label 变 mac,立即接走队列 task 2299。退役 jiu id=3
(删+launchd .disabled)+relay(.retired)。工作表 W5 改已完成。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 18:11:40 +08:00
wangjia cf3b806311 docs(ci): 补 NAS 可行性判定(DS925+ 支持 W3/W4)
read-only 实测:NAS=DS925+ x86/DSM7.3.2,本身就是 LAN DHCP 服务器
(DhcpServer 已装,引擎 dnsmasq,段 .10-.230)→DNS 下发自控、无需碰路由器;
ContainerManager 已装(可跑 AdGuard/dnsmasq 容器);DSM Reverse Proxy 已在用
→加 SNI vhost git→localhost:3000 即可;gitea 为 3000 native 进程非容器。
唯一 caveat:证书 DSM 内置 LE 仅 HTTP-01 家里签不了→走 acme.sh DNS-01(阿里云)
或同步 ali 已有 LE。判定:W3+W4 均可行,不属于「一般 nas 不支持」。
新增 §6 可行性判定表,W3/W4 状态改可行·待建,W3 修正为 NAS 自身 DHCP。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 16:47:57 +08:00
wangjia f1e527918f docs(ci): runner 走域名注册 + 局域网/外网无感切换实现方案
daily 代理换成 pangolin 全局 TUN(对 Go 二进制透明,不再掐 act_runner
直连)→ relay 淘汰,runner 统一注册到域名 https://git.51yanmei.com。
git 已在 PANGOLIN_PRIVATE_SPLIT_DOMAINS,靠 dns-system 解析:在家局域网
DNS 覆盖→LAN 直连规则先命中直达 NAS;在外公网锚点→私有域名走隧道→
pangolin1 出口→ali frp→NAS。含 SVG 架构图、机制(clientconfig 三处
注入)、六 workstream、迁移顺序、验证清单、需确认的机器改动。登记进
docs/index.html 实现计划分类。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 16:35:11 +08:00
wangjia fea6f82299 feat(ci): 多账号签名 Phase 1——SIGNING_ACCOUNT 开关 + 账号密钥集切换
按 docs/ci-multi-account-signing-design.html 落地:
- signing.env 加 SIGNING_ACCOUNT=us|cn(CI 多账号切换的唯一开关)。
- deploy-client.yml 的 macOS/iOS job 各加 "Resolve signing account" 步(从 signing.env
  读),证书/ASC 的 env 映射改为按账号三元选:us→SIGN_US_*(用户级)、cn→现有旧通用名
  (用户级,不动,供其他 app 继续用)。描述文件是 app 级(绑 bundle id)→ 仓库级,与账号无关,不变。

效果:pangolin(SIGNING_ACCOUNT=us)CI 用美国证书/ASC;其他 app 的独立 workflow 不受影响;
切账号只改 signing.env 一行。注:若 SIGNING_ACCOUNT=us 但 SIGN_US_* 未配,三元会回退到
旧(中国)名 → compile 脚本按 signing.env 的美国 Team 校验会失败(fail-safe,不会误签),
故 US 账号发版前须先配齐 SIGN_US_* 用户级 secret + 仓库级美国描述文件。

YAML 校验通过;codegen 无 drift(SIGNING_ACCOUNT 不参与 gen-signing)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 00:17:42 +08:00
wangjia 5513377221 docs: CI/CD 多账号签名设计(Apple 多账号 × 多 App,配置切换)
方案:CI 签名拆成正交两维——account(用哪个 Apple 账号,Developer ID/Apple
Distribution 证书 + ASC Key 跨该账号所有 App 共享)× app(bundle 专属描述文件)。
App 在自己仓 scripts/signing.env 声明 SIGNING_ACCOUNT=us|cn 一行完成切换;工作流
读它、三元映射从 gitea 用户级取对应账号 SIGN_<ACCT>_* 密钥集,描述文件走仓库级 per-app。
证书按账号存一次共享 + 描述文件按 App 存本仓 → 避免多账号×多App 的密钥爆炸。

含三层配置模型、gitea 命名规范、workflow 切换机制(基于现有 secret→env 映射层)、
composite action 多 App 复用、新增账号/App/切换的操作手册、Phase 0(pangolin 仓库级
快速解锁)/Phase 1(完整多账号模型)分期、待核实的 gitea 能力。已登记 docs/index.html。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-07 00:08:30 +08:00
wangjia acb9d8052b docs: runbook 修正 App ID 能力(主 app 需 System Extension)+ 收敛为 2 个 App ID
实操验证补正:§1.2 原写 4 个 App ID 各勾 App Groups+Network Extensions,漏了主 app
的 System Extension 能力——缺它 macOS 主 app 的 Developer ID profile 不含
com.apple.developer.system-extension.install,构建报「profile doesn't include the
System Extension capability」。改为 2 个唯一 App ID(iOS/macOS 共用同串):主 app 需
App Groups+Network Extensions+System Extension,扩展需 App Groups+Network Extensions;
并说明改能力后 profile 变 Invalid 须重新生成。

背景:macOS Developer ID 签名链已本地端到端验证通过(build→重签→codesign 核验:
Yanmei AI LLC / 44WULXM6SV / com.yanmeiai.pangolin,零中国痕迹)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 23:17:47 +08:00
wangjia 7be0eb4c08 docs: 迁移 Runbook 更新——代码侧已完成(signing.env 单源 + codegen)
Team ID 已知(44WULXM6SV),§3 代码替换已落地并收敛为单一真相源:改写 §3 为
「signing.env + gen-signing.mjs codegen + drift 闸」,占位 <NEW_TEAM> 全替为真值,
顶部进度卡与 §6 责任表标注代码侧完成。剩余为 Apple 后台建资产/本机公证/gitea/验证。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 22:06:36 +08:00
wangjia 3fb645ed40 refactor(signing): 原生侧签名标识符改由 signing.env codegen 生成(单一真相源)
承接 scripts/signing.env(shell 单源):Xcode/Swift 侧的 Team ID/Bundle ID/App Group
等无法 source 变量、只能字面量,故用 codegen 保持单源。

- 新增 scripts/gen-signing.mjs:从 signing.env 读 3 基值,用**结构化正则**(匹配
  `DEVELOPMENT_TEAM = X`/app group 形状等,不含任何旧字面值)回填 16 个原生文件的
  标识符;--check 为 drift 模式。因不含旧值,codegen 自身无痕、未来再迁账号仍生效。
- ci/check-codegen-drift.sh 追加签名 drift 闸(gen-signing.mjs --check)。
- 全量迁到美国身份:Team 44WULXM6SV / 实体 Yanmei AI LLC / Bundle com.yanmeiai.pangolin
  (+.PacketTunnel)/ App Group macOS 原生 44WULXM6SV.com.yanmeiai.pangolin、iOS
  group.com.yanmeiai.pangolin。清理注释/ORGANIZATIONNAME 等 Apple 侧残留痕迹。
- CLAUDE.md 同步新身份 + 指向 signing.env 单源。

验证:plutil lint 6 份 entitlements/plist 全 OK;macOS/iOS pbxproj `xcodebuild -list`
可解析、Runner build settings 解析为新 Team/Bundle;gen-signing.mjs --check 幂等 0 drift;
完整 codegen drift 闸(token/l10n/proto-i18n/signing)全绿。

注:未动 Android 包名 com.pangolin.pangolin_vpn(Play 身份、自签、非 Apple 账号痕迹,
改动影响大另议)与 Windows/桌面少量元数据串。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 20:14:30 +08:00
wangjia 6618929092 fix(l10n): 断开确认三键补进 strings.json 单源(此前只手改了生成物)
断开确认功能(1cc5207)当时直接手改了 strings_*.dart(生成文件),没补进真源
design/i18n/strings.json。codegen drift 闸重生成时会按 strings.json 把这三键冲掉,
且 app_text.dart 仍声明抽象 getter → 生成物缺实现会编译失败。

补 disconnectTitle/Body/Action(en/zh/es/ja/ko/ru 六语)进 strings.json;重生成后
strings_*.dart 内容与已发布逐字一致(仅改由手改改成走单源)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 20:13:46 +08:00
wangjia baaf21df36 refactor(signing): shell 签名身份抽成单一真相源 scripts/signing.env
迁移美国账号引出的诉求:Team ID/实体名/Bundle ID/App Group 原本散在三个签名脚本里
硬编码,迁一次账号要改多处易漏。抽成 scripts/signing.env 单一真相源,三脚本
(local_test.sh / ci/compile-macos.sh / ci/compile-ios.sh)source 它并派生各自常量。

signing.env(非机密,入 git)含:SIGNING_TEAM_ID / SIGNING_ENTITY / SIGNING_APP_BUNDLE_ID
/ SIGNING_EXT_BUNDLE_ID / SIGNING_{MACOS,IOS}_APP_GROUP + 派生的 Developer ID /
Apple Distribution 签名主体串。当前值已是美国 Yanmei AI LLC(Team 44WULXM6SV,
Bundle com.yanmeiai.pangolin);今后迁账号/改 Bundle ID 只改这一个文件。

bash -n 三脚本语法通过;source 冒烟各派生值正确。Xcode 侧单源(Signing.xcconfig)
另提交,由一致性闸比对防两处漂移。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 20:03:43 +08:00
wangjia 47c1e3329f docs: Apple 开发者账号迁移 Runbook(中国→美国 Yanmei AI LLC)
完整迁移流程:把 Pangolin 的 Apple 签名从中国「岩美北京技术」(Team BYL4KQHMTN)
整体迁到美国「Yanmei AI LLC」,目标产物+仓库无中国账号痕迹。干净切割方案(全新
Bundle ID com.yanmeiai.* + 全新 Team/证书/描述文件/公证凭据,零依赖旧账号)。

含:命名总表、Apple 后台建资产逐步(App ID/App Group/两证书/四描述文件/ASC Key)、
本机钥匙串+公证凭据配置、仓库 Team ID/Bundle ID/App Group/实体名全量替换点清单、
gitea 12 个 Apple secret 换新、无痕验收清单、责任顺序表。已登记 docs/index.html。

代码替换待用户提供新 Team ID + 证书全名后单独一批执行。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 19:42:46 +08:00
wangjia 4a76134ca9 feat(server/pay): 查单对账开通(reconcile-on-read)——付完不再干等 webhook
「付完刷新等十几秒」的服务端主因:GetOrder 的 activated 只认本地 row=paid,而 row
只有 webhook 到账才翻;webhook 投递+退避重投正是那十几秒。GetOrder 其实早已调 pay
网关查单(st),却只当 pay_status 展示、没用来开通。

改法(仅 pangolin,pay 无需改):GetOrder 拿到网关 st 后,若 st.Status=="paid" 但本地
row 未 paid → 用 st 拼 webhookEvent 就地调 settle(复用 webhook 同一段幂等开通逻辑),
把「等 webhook」压成「付完下一拍查单即开通」。webhook 保留作兜底。

- Handler 加可选 settler(orderSettler 接口,*WebhookHandler 已满足)+ SetSettler;
  main.go 装配时 payHandler.SetSettler(payWebhook)。
- 幂等双保险:row.Status!="paid" 守卫 + settle 内锁行/已 paid 短路 → 重复轮询/并发查
  不二次开通、不二次发奖。settle 失败仅记日志,退回旧展示由下一拍或 webhook 兜。
- channel 查单不返回 → 回退 row.Method(仅影响渠道显示)。成功状态词 "paid" 已对齐
  pay 仓 model.OrderStatusV2,提为 payOrderPaid 常量并注释「改前对齐 pay 契约」。
- 信任模型一致:主动调 pay 自有接口(server→server,order_no 持有凭据),与现有把
  st.Status 当 pay_status 透传同源,非接收外部推送,不需 webhook 那样验签。

测试:新增端到端 sqlite——网关 paid→就地开通(activated + 台账翻 paid + 真授予订阅)
且重复轮询幂等(仍 1 条订阅);网关 pending→不开通。go test ./... 全绿、vet 干净。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-06 09:37:54 +08:00
wangjia 55f6b1a19e fix(client/pay): 优化付款流程两处体感——点击即时反馈 + 到账更快反映
复盘反馈:①点「立即购买」有一点卡顿 ②付完刷新不及时、等了十几秒。

① 卡顿:_buy 原本 await createOrder 网络往返、返回才导航,期间购买页零反馈 →
   改为 start()(同步先置 creating)后立刻导航到支付页,用户点击即见页面切换+转圈
   (支付页已有 creating/failed/succeeded 全相位渲染);加 _buying 防连点(避免下双单)。

② 刷新慢(客户端可控部分):
   - _startPolling 首拍立即发(原 Timer.periodic 要干等一个周期才首查);
   - 新增 AppLifecycleListener,付款后从微信/支付宝返回 app 即刻补查一拍
     (等待付款期切后台,iOS 会挂起 Dart Timer,回前台首拍可能迟到十几秒——主因);
   - 轮询间隔 3s→2s。

测试:payment_flow 新增「start 立即补一拍→已到账不等周期直接 succeeded」,
共 7 个单测 + 支付页 widget 全绿;flutter analyze 干净。

注:十几秒还有服务端一半——GetOrder 的 activated 只认本地 row=paid(仅 webhook 落),
即便主动查 pay 网关已 paid 也不即时开通。reconcile-on-read(查单时网关已成功即幂等 settle)
是更大且触及结算逻辑的改动,另议。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-05 19:52:43 +08:00
wangjia 721371d806 fix(server): 付费会话「活跃即续期」——根治 macOS 常驻隧道一天多掉线
根因:付费连接凭证硬编码 24h TTL(paidCredentialTTL),而客户端只在 _connect()
(用户/看门狗前台重连)才重签,服务端从不因流量续期。macOS sysext(root)隧道独立于
GUI app 常驻,用户关窗后 Dart 看门狗根本不运行 → 凭证 24h 到期、下次 agent 重注册
用「未过期」快照整表覆盖并重渲染 sing-box → REALITY 会话被剔除、永久黑洞。该逻辑
四端共用同一份 Dart,故为全端共性(macOS 最易现形)。

修法(方案 A,服务端、与客户端生命周期无关,一改修四端):
- ReportUsage 收到某 dp_uuid 有流量,若属付费套餐(!AdGate)即把其凭证 expires_at
  顶到 now+PaidCredentialTTL。活跃会话永不过期;免费凭证 TTL 编码日额度、绝不续期
  (否则击穿日限)。每报按 user 缓存一次 entitlement 查询。
- 新增 NodeStore.RenewCredential(纯 UPDATE,WHERE expires_at>now 不复活已过期会话)。
- 24h 提为 nodes.PaidCredentialTTL 单一真相源,httpapi 引用它消除漂移。
- 纯 DB 续期,无需再 push agent(现有 REALITY 用户仍在,只要 DB 行不过期,下次
  重注册快照仍含它)。可移植 SQL(? 占位 + Go 端算时间,无 MySQL 专属构造)。

测试:handler 层付费续期/免费不续期(mock);store 层真 SQLite 续期/不复活已过期。
go test ./... 全绿、go vet 干净。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-05 19:34:58 +08:00
wangjia 1cc52075e9 feat(client): 断开连接前弹二次确认框(避免误触瞬断)
连接键在 on 态点击不再直接断开:先弹危险操作确认框(告知连接已加密,
确认才 toggle 断开);off/connecting 态行为不变直接 toggle。
新增 l10n disconnectTitle/Body/Action(en/zh/es/ja/ko/ru 六语)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-09-05 19:08:01 +08:00
wangjia def3bb34b8 fix(server): 控制面 8080 收口——默认绑 loopback + 部署时幂等纠偏 ADDR/ufw
Deploy Server / deploy-server (push) Successful in 4m33s
Deploy Site / deploy-site (push) Successful in 2m19s
Deploy Client / build-windows (push) Successful in 1m39s
Deploy Client / build-android (push) Successful in 3m49s
Deploy Client / build-macos (push) Failing after 1m47s
Deploy Client / build-ios (push) Failing after 16m18s
Deploy Client / release-deploy (push) Successful in 1m39s
现网 server.env 漂移成 ADDR=:8080(全网卡),ufw 又放行 8080 → 控制面 API 明文
裸奔公网(http://<IP>:8080),绕过 cloudflared/CF 的 TLS 层。两处硬化:
- main.go:ADDR 未设时默认由 :8080 改 127.0.0.1:8080(防御纵深;跨主机监听须显式设 ADDR)
- deploy-server.sh:部署重启前幂等把 ADDR=:8080 纠回 127.0.0.1:8080(备份原文件)+
  撤 ufw allow 8080/tcp。收口随 server-v* 发版自动落地,不再手改线上。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
server-v1.1.2 site-v1.1.2 client-v1.1.4
2026-08-09 10:02:10 +08:00
wangjia bb031a04c1 fix(client/web): 语言跟随系统 + SSO 落地补 basePath + 移除账户页续费按钮
- 语言(#1):LocaleNotifier 首启无用户选择时读设备 locale(zh/ja/ko/ru/es 命中
  对应语种,余回退英文),原来恒为固定默认 en、从不看系统语言。用户显式切换仍持久
  化优先。
- 用户中心 SSO(#3):usercenter 运行在 basePath=/user,但 /sso 落地页用原生
  window.location.replace('/') 跳转(Next 不补 basePath)→ 落到站点根=官网主页,
  且换票已建立的会话看起来像未登录。改为 withBase() 统一补 /user 前缀,兑票成功
  直达 /user/ 且保持登录态。(服务端 issue/exchange 流程实测均 200,非后端问题)
- 账户页(#4):移除"续费/升级"按钮(免费卡片 + PRO 横幅两处)及其未用组件;购买入口
  仍由"购买套餐"行保留,不影响下单路径。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-08-09 09:42:25 +08:00
wangjia e7e59a786d fix(client): 默认 UI 品牌文案统一英文 Pangolin(窗口标题/托盘/安装包名/macOS 菜单栏)
默认 UI 语言是英文,但窗口标题(windowManager.setTitle)、MaterialApp title、
托盘 tooltip 与右键菜单、Windows 安装包显示名、macOS CFBundleName 硬编码中文,
中英混排。统一改英文品牌名;iOS 桌面图标名(CFBundleDisplayName=穿山甲)按
市场定位保留。托盘菜单文案后续可接 l10n(现契约无 tray 键)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-08-09 01:43:39 +08:00
wangjia b59101abf1 chore(scripts): local_test 默认 API 改指生产域名(8080 公网收口后旧默认失效)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-08-09 00:54:03 +08:00
wangjia 0ddcc5e935 fix(client/macos): CI 重签 sysext entitlements 补 network.client/server + 闸断言
Deploy Client / build-windows (push) Successful in 1m43s
Deploy Client / build-android (push) Successful in 5m13s
Deploy Client / build-macos (push) Successful in 6m53s
Deploy Client / build-ios (push) Failing after 12m28s
Deploy Client / release-deploy (push) Successful in 2m6s
v1.1.2 事故第二弹:inside-out 重签的 sysext heredoc 漏了
com.apple.security.network.client/server —— 沙箱扩展无权对外建连,
隧道起了也连不上节点(本地 Xcode 直签用仓库 PacketTunnel.entitlements
带这两键,故 local_test 流程正常,唯 CI 包不通)。

- compile-macos.sh / local_test.sh cmd_sign:sysext heredoc 补两键(与仓库源文件对齐)
- 公证前一致性闸扩展:断言 sysext 最终签名 network.client/server=true,重签丢键即 fail
- 已本地验证:对 v1.1.2 发布包用新 entitlements 重签,读回签名两键在位

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
client-v1.1.3
2026-08-09 00:49:05 +08:00
wangjia 0c6957fcb5 chore(todo): #28 App Group 修复已发版 client-v1.1.2,标 done 待验收
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-08-09 00:07:14 +08:00
wangjia 6a4da3e164 chore(todo): 记 #28 App Group 事故复盘 + #29 chenxin 凭据过期 + #30 四语支付文案缺口
Deploy Client / build-windows (push) Successful in 1m43s
Deploy Client / build-android (push) Successful in 6m57s
Deploy Client / build-macos (push) Successful in 8m43s
Deploy Client / build-ios (push) Successful in 4m53s
Deploy Client / release-deploy (push) Successful in 2m6s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
client-v1.1.2
2026-08-08 23:41:36 +08:00
wangjia 77302cfa7a fix(client/macos): CI 重签 App Group 修回 macOS 原生格式 + 公证前一致性闸
compile-macos.sh 的 sysext entitlements 曾硬编码 iOS 风格 group. 前缀,且主 app
entitlements 缺 application-groups —— 与 NEMachServiceName 前缀对不上,
nesessionmanager 报 Code=6,sysextd 当场卸载新扩展,线上包永远建不出 VPN 配置
(本地 Xcode 直签流程正常,故长期未察觉;详见 todo #28)。

- compile-macos.sh: 两处 entitlements 改用 ${APP_GROUP};公证前加一致性闸
  (读最终签名核对 app/sysext App Group 与 NEMachServiceName 前缀,不一致即 fail)
- local_test.sh: 同款修复 + verify_app_group 闸 + cmd_bump_build 自动递增
  sysext 构建号 + macos 一条龙子命令(bump→build→闸→公证→装→跑)
- pbxproj: CURRENT_PROJECT_VERSION 53→55(本地已构建两次,保持单调)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
2026-08-08 23:41:36 +08:00
wangjia 79830cb43f fix(ci): client release-deploy 加 if 守卫,mac/ios 抖失败不阻发布
Deploy Client / build-windows (push) Successful in 1m42s
Deploy Client / build-android (push) Successful in 5m8s
Deploy Client / build-macos (push) Successful in 8m3s
Deploy Client / build-ios (push) Successful in 4m59s
Deploy Client / release-deploy (push) Successful in 2m2s
run 239:android/windows/macos 全绿、ios 在 Checkout 阶段 git fetch
git.51yanmei.com 走 frps 超时挂掉(非 iOS 构建问题;run 238 同代码 fetch 碰巧
成功)。因上一版把 build-ios 加进 release-deploy 的 needs,而 gitea 里
continue-on-error 的 job 失败**仍会 skip 下游 needs** → release-deploy 被跳过。

修:needs 保留四平台(等全部完成、不抢跑 → 收齐已上传产物),但加
`if: always() && needs.build-android.result == 'success'` —— 只要 android
(稳定 floor)成功即发布,mac/ios/windows 的瞬断失败不再 skip release-deploy,
download-artifact 收当时存在的产物照发。

yaml 校验通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
client-v1.1.1
2026-08-03 00:17:53 +08:00
wangjia fdc13ea06c fix(ci): client release-deploy 挪到 nas runner + 等全平台构建
Deploy Client / build-windows (push) Successful in 1m41s
Deploy Client / build-android (push) Successful in 4m40s
Deploy Client / build-macos (push) Successful in 7m30s
Deploy Client / build-ios (push) Failing after 11m51s
Deploy Client / release-deploy (push) Has been skipped
client-v1.1.1 首跑:4 平台 build 全绿,但 release-deploy 卡在「Release →
Forgejo」curl exit 28(超时,3m3s=release_ensure 60s×3 重试耗尽)。根因:
release-deploy `runs-on: mac`(mac-pangolin-2)访问 git.51yanmei.com 走 frps
隧道 → 抖断。deploy-server/deploy-site 同类 Forgejo release 步跑在
ubuntu-latest(nas act_runner,与 gitea 同机/同网)稳定通过(server 仅 13s)。

- runs-on: mac → ubuntu-latest:release-deploy 全为网络/SSH 步骤(下载产物/
  传 Forgejo/SSH pangolin1/通知),无 mac 专属需求,挪到 nas runner 走本地。
- needs: [build-android] → [build-android, build-windows, build-macos, build-ios]:
  等全部平台 build 完再发布,修旧竞态(只等 android → macos/ios 产物没传就发布
  漏平台)。macos/ios 保留 continue-on-error,失败不阻断已成功平台。
- 更新过时注释(原称 Apple secret 未配 / needs 仅 android+windows)。

yaml 校验通过。build 四平台本身已在 run 639 全绿,本次只修发布环节。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-08-01 23:47:29 +08:00
wangjia f0f845c6e5 fix(ci): site 流水线加 npm 国内镜像(GFW)+ npm ci 重试
Deploy Site / deploy-site (push) Successful in 2m6s
Deploy Client / build-windows (push) Successful in 1m43s
Deploy Client / build-android (push) Successful in 20m37s
Deploy Client / build-macos (push) Successful in 8m19s
Deploy Client / build-ios (push) Successful in 5m29s
Deploy Client / release-deploy (push) Failing after 24m14s
site pipeline(deploy-site.yml:Astro 官网 + Next.js 用户中心 + npx wrangler)
在墙内 nas runner 上 npm ci / npx wrangler@4 直连 registry.npmjs.org → 慢/抖断。
_env.sh 早有 Go(goproxy.cn)/Flutter(flutter-io.cn)镜像,唯独漏了 npm,且 3 个
site 脚本根本没 source _env.sh。上次「修外网发版 GFW 坑」只覆盖 android(gradle)/
windows(maven),site 这块遗漏。

- _env.sh:加 NPM_CONFIG_REGISTRY=registry.npmmirror.com(可 env 覆盖)+ npm_ci_retry
  助手(照 flutter_pub_get_retry,5 次重试)。
- compile-site.sh / compile-usercenter.sh:source _env.sh,npm ci → npm_ci_retry。
- deploy-site.sh:source _env.sh,让 npx wrangler@4 也走镜像拉包。

本地实跑 compile-site.sh:镜像生效、Astro 构建成功、dist/ 产物齐全(index.html 48K
+ 多语言目录)。shellcheck -x 全 clean。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
site-v1.1.1
2026-08-01 20:32:37 +08:00
wangjia b255fdfb4e chore(ci): CI 校验迁本地(ci/check-local.sh),删 pipeline ci.yml
ci.yml 跑在自建 gitea 的 mac-pangolin-2 runner 上,该 runner 不稳:重 job
(go/flutter/golden/integration/e2e)频繁整体超时/失败(run 233、236 复现:所有
runs-on=mac 的 job 全挂、runs-on=ubuntu 的 nas 快扫描全过,证明是 mac runner
掉线而非代码)。既然开发就在 mac 上、原生工具齐全,把校验迁回本地。

- 新增 ci/check-local.sh:与 ci.yml 各 job 一一对应,dev mac 原生跑(仅 golden
  因 Linux 权威基线走 docker)。分档:默认=静态闸+go build/test+flutter analyze/test
  (各带覆盖率闸 Go30%/Flutter28%);--full 加 golden/go-integration/e2e;
  --only <名> 单跑;--list 列项。缺工具标 SKIP 不算失败。shellcheck-clean。
- 删 .gitea/workflows/ci.yml(部署流水线 deploy-server/client/site 保留,发版
  仍过 go test)。
- .githooks/pre-commit:秒级快闸不变,指引改指 ci/check-local.sh(原指 CI)。
- CLAUDE.md CI/CD 段重写 + ds-flow 闸表「CI」触发点改「check-local」。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-08-01 10:20:18 +08:00
wangjia 9aafb63461 chore(todo): 同步最新看板到 main(#24-#27 + #23→done)
Deploy Server / deploy-server (push) Successful in 4m7s
主 checkout 停在陈旧 feat/private-dest-acl,其工作区 todo 看板反而领先 main
4 条(#24 UI连接态脱钩 / #25 私有服务在家直连 / #26 MySQL Promo 并发防线 /
#27 分支审核 Minor 硬化)且 #23 已推进到 done。工作区是 main 的干净超集
(main 无任何独有条目),取工作区版覆盖回 main,零丢失。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
server-v1.1.1
2026-08-01 02:12:34 +08:00
wangjia 6f232d4043 fix(routing): 堵住 ip_cidr direct 旁路系统层 + 客户端保存失败可见提示
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 25s
ci-pangolin / Cleartext Scan — Android 禁明文 (push) Successful in 19s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 18s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (push) Failing after 13m6s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 13m16s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Failing after 13m25s
ci-pangolin / Go — build + test (push) Failing after 13m35s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (push) Failing after 13m45s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Failing after 13m54s
ci-pangolin / Flutter — analyze + test (push) Failing after 14m5s
ci-pangolin / OpenAPI Sync Check (push) Failing after 14m16s
ci-pangolin / Lint — shellcheck (push) Failing after 14m27s
分支审核发现两处 Important,合并前修复。

① [安全] direct 的 ip_cidr 用户规则可自伤式旁路整条隧道:
   Validate 原先只校 CIDR 语法。用户提交 ip_cidr=0.0.0.0/0 action=direct
   (或 172.16.0.0/12,含隧道 DNS 172.19.0.2)会并入 TUN 入站
   route_exclude_address(OS/auto_route 层,位于系统强制层之下),被排除的
   流量根本不进 sing-box → hijack-dns 与整条隧道被静默旁路,违反「系统层
   用户不可越」铁律。
   - Validate: direct 的 ip_cidr 拒绝 catch-all(/0)及与保留段 172.16.0.0/12
     重叠(写入闸)。
   - clientconfig 渲染层:新增 routing.SafeToExclude 守卫,只有安全的 direct
     ip_cidr 才并入 route_exclude_address(纵深防护,兜底写入闸之前的历史坏行)。
   - 测试 TestValidateDirectIPCIDRReservedGuard 钉死:拒 catch-all/隧道段重叠、
     放行 proxy catch-all 与不重叠 direct。

② [健壮性] 客户端保存失败静默回滚 + 抛未捕获异步异常 + 对话框无字段校验:
   _persist 失败会 rethrow(约定调用方 catch),但屏幕层所有回调
   (setMode/setBuiltin/addRule/removeRule/reorder/resetToDefault)均未 catch,
   规则闪现即消失、无提示,且 rethrow 变 zone 未处理异常。
   - 新增 _guardSave 守卫:await + 失败弹 SnackBar(AuthApiException 显服务端
     双语文案含校验错,其余回退通用「保存失败」),包裹全部变更类回调。
   - 添加规则对话框:_valueError 字段级预校验(ip_cidr 用 InternetAddress
     校验、geo 白名单仅 cn),非法即禁用保存并内联红字提示;语义级(保留段)
     仍由服务端权威判定经 SnackBar 呈现。
   - l10n 单源新增 routingSaveFailed / routingRuleValueInvalid(6 语),regen。

go test ./... 全绿;flutter analyze 无 error;flutter test 265 全过无 golden 回归;
codegen 幂等、原型 i18n 无漂移。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-08-01 00:32:52 +08:00
wangjia e157b16c11 fix(server): 走隧道/拒绝的域名规则也开 reverse_mapping
用户规则(可配置分流)里 action==proxy/reject 的域名规则,此前不触发
dns.reverse_mapping(只有 direct 域名规则触发)。而应用自行解析域名后按 IP
发起连接,路由层只剩 IP,无反向映射则 domain 规则永不命中——走隧道/拒绝的
域名规则会静默失效。

translateUserRules 把 hasDomainDirect 扩成 hasDomainRule:任意 action 的
域名类规则(domain/domain_suffix/domain_keyword)都置真、都开 reverse_mapping。
direct 专属的 ip_cidr→route_exclude_address(extraExclude)那条线不变。

这也是把私有服务分流(PANGOLIN_PRIVATE_SPLIT_DOMAINS)改用用户规则表达的
前置修复——否则一条"走隧道"用户规则替代 private-split 会连不上。

回归测试 TestBuildConfigProxyDomainEnablesReverseMapping:唯一一条走隧道/拒绝
域名规则(privateSplit 关、无 direct 域名规则)必须开 reverse_mapping。go test ./... 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-07-31 01:15:34 +08:00
wangjia ce485a1b62 fix(ui): SegSwitch 用 FittedBox(scaleDown) 防窄容器截断文字
添加规则弹层的动作段选(Direct/Tunnel/Reject)在窄弹层里被 icon+文字挤到 ellipsis 截断
成「Dire…/Tun…/Reje…」。改:段内容包 FittedBox(scaleDown)——放得下原样(宽段选/goldens
不变),放不下整体等比缩放而非截断;padding 16→12 留余量。flutter test 265/265 无 golden 回归。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-07-29 08:42:27 +08:00
wangjia 49d0c5d2df docs(claude): 记 iOS 真机装机(local_test.sh ipad,老忘)+ CI 发版(tag触发+私有依赖鉴权)+ 可配置分流功能
- 移动端段加真机装机:API_URL=... local_test.sh ipad <设备>(公司分发证书/签名核验/xcurl 装机);
  iOS libbox gitignore 产物从主仓拷免重建;新设备 -allowProvisioningDeviceRegistration。
- CI/CD 段更新:tag 触发发版(server-v*/client-v*/site-v* → compile→test→release→deploy pangolin1
  备份+migrate+回滚+healthz),取代过时的「仅校验无部署」;私有依赖 github.com/wangjia/codes→gitea
  鉴权(GOPRIVATE+insteadOf,冷缓存 runner 必踩);发版排障(gitea actions API + rbw gitea 读写key)。
- server/ 加可配置分流小节:routing_profiles/GET-POST me/routing/BuildClientConfig 翻译(层级/三模式/
  IP直连 route_exclude 在 tunIn/域名直连 reverse_mapping/nil 逐字节不变)/system_locked_domains。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-07-29 08:23:42 +08:00
wangjia cd0ba919d1 fix(ci): deploy-server 编译前给私有依赖 github.com/wangjia/codes 配 gitea 鉴权
Deploy Server / deploy-server (push) Successful in 4m17s
冷缓存 runner 上 go build 拉私有 codes 依赖时,GOPROXY=goproxy.cn 对私有仓 404 →
回退 direct git 到 github.com → 无凭证失败(server-v1.1.0 首发在此挂,44s Compile 步红)。
加一步:GOPRIVATE + git insteadOf 把 github.com/wangjia/codes 重写到自建 gitea 并注入
FORGEJO_TOKEN(已验证 oauth2:<token> basic-auth 可 ls-remote 到 pin 的 commit)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
server-v1.1.0
2026-07-29 07:50:35 +08:00
wangjia a307c6ca2c fix(test): codes-lib 迁移往返测试改用 m.Migrate(21) 显式定位版本
Deploy Server / deploy-server (push) Failing after 41s
TestCodesLibMigrateRoundTrip 硬编码 6 次 Steps(-1),假设 000027 是迁移栈顶;本功能
加了 000028_routing_profiles 后 6 步落在版本 22、跳过了 000022 down,导致断言失败。
根因不是迁移 SQL(000022 up/down 正确、codes-lib 表无 FK——推翻了 FK 假设),而是
测试的脆弱步数计数被新增迁移打乱。改用 m.Migrate(21) 显式降到 000022 down 之后的
边界,不受栈顶新增迁移影响。全仓 go test ./... 恢复 0 FAIL。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-07-29 07:08:56 +08:00
wangjia 2930d76cf3 feat(routing): 域名级系统锁冲突提示 + 重置默认按钮 + 客户端 follow-up 清理
承接 FT-A(GET /v1/me/routing 含只读 system_locked_domains)。

- RoutingProfile 加只读 systemLockedDomains(fromJson 读/toJson 不输出);
  routing_screen 冲突检测扩展到域名类规则(domain/domain_suffix/domain_keyword
  命中锁定域名 → systemLocked),ip_cidr 私网启发式保留。
- RoutingProfile.defaults() + RoutingProfileNotifier.resetToDefault()(复用
  _persist:乐观更新/失败回滚/存成功后自动重连,保留只读 systemLockedDomains
  不丢)+ routing_screen 加「重置默认」按钮与二次确认弹层(新增 3 个 l10n 键)。
- RoutingRule.copyWith 用哨兵支持 note 显式清空为 null;RoutingRule/Builtin/
  RoutingProfile 加值相等 operator==/hashCode。
- T8 smartRouteSub 清理:grep 全仓发现 design/prototype/i18n/alias.json →
  gen_proto_i18n.mjs(CI 漂移闸)仍有活引用,按计划口径不删,详见
  .superpowers/sdd/task-FTB-report.md。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 06:57:58 +08:00
wangjia 3159dd75c1 refactor(routing): 暴露 system_locked_domains + 服务端 follow-up 清理(FK/Validate/dedup/exclude单源/共用Store/Builtin保留) 2026-07-29 06:38:52 +08:00
wangjia 52912268d0 docs(routing): 可配置分流 spec 状态 → Phase 1 已实现
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
2026-07-28 09:34:35 +08:00
wangjia 4c76c2a701 feat(routing): 存档案后连接态自动重连使新规则生效
Task 9: RoutingProfileNotifier._persist() 保存成功后,若 connectionProvider.phase
== on,触发 ConnectionController.reapplyRoutingProfile()(disconnect→connect,
不改选中节点)重连,让服务端按新档案渲染的规则生效;off 态不触发。

新增 l10n 键 routingRulesReconnecting(单源 design/i18n/strings.json → codegen),
「规则已更新,正在重连…」区别于既有 nodeReconnecting(弱网抖动语义)。

reapplyRoutingProfile 内先置 _userDisconnect=true 再 disconnect,避免其触发的
kernel off 事件被 _onKernelStatus 误判「意外掉线」、再抢跑一次 watchdog 自动重连。
2026-07-28 09:17:32 +08:00
wangjia 86e5b6d451 feat(routing): 设置入口改分流规则下钻行 + 双形态导航
- settings_page.dart: smartRoute 布尔开关行 → 分流规则下钻行(标题 + 当前模式 pill
  + chevron),点击 desktop/tablet 切 navViewProvider 内容区下钻,mobile 全屏 push
  RoutingScreen(与 account_page.dart 的 open() 双形态导航同一范式)。
- navigation_provider.dart: 新增 NavView.routing + kSettingsSubViews 集合(登记
  settings 的下钻子页,供 shell backTarget 判断返回 settings 而非 account)。
- desktop_shell.dart/tablet_shell.dart: 内容区 switch 挂 RoutingScreen(embedded:
  true),backTarget 优先判 kSettingsSubViews → 回 settings。
- settings_ia_test.dart: 追加下钻行断言(标题+模式pill+点击导航到 RoutingScreen)。
- 重录 desktop_settings golden(唯一因本刀布局变化受影响的 golden)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 09:04:46 +08:00
wangjia d73b6da110 feat(routing): 分流规则子屏 UI(模式/内置/我的规则/添加/冲突提示)
- 新增 RoutingScreen(ConsumerWidget):代理模式段选(全局/智能/直连)→setMode、
  内置规则卡(国内直连开关 + LAN 强制锁定行)、我的规则 ReorderableListView
  (拖动排序→reorder、删除→removeRule)、添加规则弹层(类型 chip + 目标输入 +
  动作段选→addRule)、FINAL 兜底行。非智能分流模式「我的规则」区灰化+忽略提示。
- 冲突提示:①同 type+value 被更靠前规则遮蔽 →「已被上面规则覆盖」(纯本地计算);
  ②自定义 ip_cidr 规则落在私网/回环地址段 → 系统锁定提示(启发式,详见文件头注释
  ——真正的私有服务域名清单未经 API 下发给客户端,是已知缺口)。
- l10n 新增 9 个 key(routingTypeDomain/DomainKeyword/IpCidr/Geoip、
  routingRuleShadowed、routingModeNote、routingRuleValueHint、routingNoRules),
  经 design/i18n/strings.json 单源 + gen_l10n_dart.mjs 生成六语。
- 补生成 pangolin_icons.dart 的 plus 图标(design/prototype/icons.js 已有,
  codegen 之前未跑过)。
- 新增 widget 测试 test/widget/routing_screen_test.dart:模式段选/规则行/添加
  按钮可见、非智能模式灰化、重复规则遮蔽提示,共 3 例。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 08:51:40 +08:00
wangjia 057c11eee2 feat(routing): 客户端 model + AccountApi + AsyncNotifier provider
- RoutingProfile/RoutingRule/Builtin 手写 fromJson/toJson/copyWith,对齐服务端
  GET/POST /v1/me/routing 契约;enabled 缺省容错默认 true,toJson 恒输出该字段
- AccountApi.routingProfile()/saveRoutingProfile() 封装两端点
- RoutingProfileNotifier(AsyncNotifier):addRule/removeRule/updateRule/reorder/
  setMode/setBuiltin 均本地乐观更新后落盘;save() 失败整体回退到变更前 state
  并 rethrow(不静默吞,交调用方处理)——Riverpod asyncTransition 的 seamless
  copyWithPrevious 会用当前已存 state 覆盖手动挂的错误值,故不用 AsyncError
  路径,改走显式回退
2026-07-28 08:27:43 +08:00
wangjia 82b3988974 feat(routing): connect 读 per-user 档案传入渲染(fail-safe 回退默认) 2026-07-28 08:11:48 +08:00