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
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
301e13f477
fix(client/payment): switchMethod 补 catch-all 兜网络/超时错(与 start/resume 对齐)防卡 awaitingPayment
...
switchMethod 的首个 retry() 调用只 catch AuthApiException,网络/超时/未知异常会穿出到
无 try/catch 的 _pickAndSwitch,UI 卡在 awaitingPayment 无任何提示。补一个 catch-all,
与 start()/resume() 同一处理形状落 failed 相位(自动经 build() 切到 _failed() 呈现);
payment_page.dart 的 _pickAndSwitch 再加一层防御性 try/catch + toast,防任何逃逸异常
变成无提示的 unhandled rejection。补单测覆盖非 Auth 异常路径。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 15:53:30 +08:00
wangjia
78fd600522
feat(client/notices): 通知页真实化+铃铛红点接 provider+进页清读+三类型标签六语
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 14:05:47 +08:00
wangjia
6a782a07fd
feat(client): notices api + provider(未读计数/markAllRead/延迟首拉)
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 13:48:12 +08:00
wangjia
66422f92ce
fix(client): created 单详情补「取消订单」按钮(原型 pending 双按钮早有规格,代码漏)+ 自动连接 toast 3s→1.5s
...
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 19s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 23s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 19s
ci-pangolin / Lint — shellcheck (pull_request) Successful in 6s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 34s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 36s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 5s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 4m42s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 21s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 10m19s
ci-pangolin / Go — build + test (pull_request) Failing after 10m26s
- orders 详情 created 态:继续支付(主)+ 取消订单(ghost)——orders.html/ui-desktop 原型
pending 态早已登记双按钮,Flutter 侧漏实现(代码落后原型)。取消走远程 cancel,
失败(如其实已付,pay 409)不报错,统一 invalidate 刷新以最新状态为准。
- 自动连接「正在自动连接到 X」toast 缺省 3s 用户反馈偏长 → 1.5s(与「已复制」1.4s 同量级)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-13 11:43:48 +08:00
wangjia
92943258ab
fix(client/orders): 继续支付改为 retry 恢复原单会话(原误进购买页致重复下单);409 已付自纠刷新
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 12s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 25s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 20s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 32s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 21s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 38s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 2s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Failing after 13s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 10s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 4m50s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 20s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 11:29:25 +08:00
wangjia
3d24451835
fix(nodes): 桌面 Nodes 页统一 600 宽度(原型挂 view-body + Flutter PageBody)+ 双列网格改单列镜像原型
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 10:13:48 +08:00
wangjia
a61641b53b
refactor(client): SubScaffold 六合一(视觉零变化)+ tablet 注释订正 + 平板通知铃入口( #20 )
2026-07-13 10:07:18 +08:00
wangjia
fa9a5c2d5e
design+client: 抽 .view-body/PageBody 统一桌面内容页宽度(600/wide 840)——stats·contact 补约束,settings·contact 560→600,九页收口
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
2026-07-13 09:46:57 +08:00
wangjia
0b24e26fb6
feat(client/auth): 注册页加邀请码(选填)输入,提交时传 invite_code
2026-07-13 08:00:25 +08:00
wangjia
e061237496
feat(client/invite): 邀请页真实化(邀请区+战绩+TG 任务卡)+ 六语 l10n
2026-07-13 07:51:19 +08:00
wangjia
cdf67a200c
feat(client): invite api + provider + auth_api.register 加 inviteCode
2026-07-13 07:39:01 +08:00
wangjia
252779c607
fix(client/pay): CNY 渠道 method 对齐生产哪吒(nezha)+ 下单失败不再静默
...
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 24s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 19s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 45s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 20s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 37s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 3s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Failing after 15s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 10s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Successful in 4m41s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 20s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 12m35s
根因(购买按钮全端「点了没反应」):
- 生产 pay-v2 的 CNY 渠道是哪吒(nezha)聚合(底层支付宝/微信),客户端却给
CNY 发 method=alipay → pay 不认 → POST /api/v2/orders 400。USDT/crypto 一直正常。
- 且 _buy 只在 awaitingPayment 时导航,下单失败(phase=failed)既不导航也不弹错,
非 Auth 异常还直接从 start() 抛出未捕获 → 用户看着「没反应」。
改:
- PayChannelX.method:cny 'alipay' → 'nezha'(usdt 仍 crypto)
- payment_provider._metadata 法币条件同步 nezha;start() 补 catch(_) 兜非 Auth 异常
落 failed(状态机不再卡 creating)
- purchase_page._buy:phase==failed 时 showPangolinToast 显式提示
- payment_page 换支付方式的另一渠道 alipay → nezha
- 测试钉:PayChannel.cny.method == 'nezha'(发错 method 会被 pay 400)
nezha 返回 render_type=redirect(payload.url),客户端现成 redirect 分支直接接;
orders_page 早有 'nezha'→payMethodNezha 标签。服务端无需改(method 透传、
SettlementCurrency 只区分 crypto vs 法币)。
验证:flutter analyze 干净;payment 相关 18 测试全过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 22:09:31 +08:00
wangjia
17d99acf62
feat(client/purchase): 全员优惠月付特殊卡片(¥6/$1,随渠道显价)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 19:34:07 +08:00
wangjia
04d5cbc11f
feat(l10n): 账户入口 redeemEntry 改「Redeem」(去掉「& buy」,购买已独立成购买套餐行)
...
6 语言:Redeem/兑换/コード引き換え/코드 등록/Активация/Canjear;account golden 重录。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 18:54:58 +08:00
wangjia
2416c943cb
refactor(client): 删除死代码 PlansScreen + NavView.plans + choosePlan l10n(Renew→Purchase 直达后无调用者)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 18:53:10 +08:00
wangjia
0421c86099
test(golden): 重录 IA 漂移的 7 个 desktop golden(侧栏 Account + 账户页重排导致 shell 变化)
...
account/contact/devices/plans/redeem/servers/stats —— 均因 desktop_shell 侧栏加 Account 一级项 + account_page 重排,渲染变化,非 bug。full suite 232 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 18:31:56 +08:00
wangjia
5cf42a64c8
feat(settings): 删购买/订单卡 + 归连接/外观/关于三组 + Web 链接下沉底部
...
购买套餐/我的订单入口已归 Account 页,设置页不再重复;设置页重组为
「连接」(开关组)/「外观」(语言+深色)/「关于」(协议/检查更新/版本+
Web 用户中心链接下沉底部)三组,标题走 l10n settingsGroupConn/Appearance/About。
新增 settings_ia_test.dart(TDD)+ 重录 desktop_settings golden。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com >
2026-07-12 18:10:37 +08:00
wangjia
b839c77712
feat(account): IA 重排+邀请入口+删偏好卡+设置/联系 formFactor 门控
...
账户页重排为:账户信息 → 购买套餐/我的订单/兑换码(三行合一) → 邀请好友(新增,
gift 图标+t.inviteEntry/inviteEntrySub,接 NavView.invite) → 设备(常显)/
设置/联系(仅 mobile,桌面侧栏已有同入口,formFactor 门控避免重复) → 退出。
删语言/主题/协议卡及仅供其用的 _LangSwitch/_PangolinSwitch(l10n getter 保留,
Settings 页仍用)。_NavRow 加可选 subtitle 用于邀请卡副标题。
新增 InviteScreen(client/lib/screens/invite_page.dart)占位屏:邀请码/链接卡
(可复制,值写死 PANGOLIN-DEMO)+ 奖励规则三条 + 我的奖励占位卡,顶部
TODO(spec-2) 标注接口点(邀请码/链接由后端签发,奖励进度来自 /v1/invite)。
顺带修复 desktop_shell.dart 非穷尽 switch 编译错误(NavView 加 invite 后未接线,
整个 client 目前无法编译);notifications 分支占位回落账户页,留给 Task 6 接线。
test/widget/account_ia_test.dart 新增桌面态 IA 结构断言(邀请入口存在/语言不在/
兑换在);account_desktop_nav_test.dart 同步去掉桌面态设置/联系两个失效断言
(改走桌面侧栏,非账户卡),加邀请入口断言。desktop/tablet 账户页 golden 重录。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:44:51 +08:00
wangjia
c3d32a476e
fix(client): 联系/购买渠道卡点击外部打开(修空 onTap)+ 桌面账户子导航复现测试
...
- channel_launch: @→t.me / 邮箱→mailto / 域名→https,外部打开
- 联系页(桌面)/联系&兑换子页(移动)渠道卡接上 onTap
- account_desktop_nav_test: 证明桌面设置/兑换/联系/订单导航正常(231 全绿)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 06:54:18 +08:00
wangjia
272eca04ce
feat(client): pay v2 支付渠道 switch + 订单列表/详情端到端
...
购买/支付(Phase B):
- 新 SegSwitch 段控 widget(镜像 .segswitch 原子)
- 购买页重构:顶部支付渠道 switch(默认 USDT→加密货币 / 人民币→支付宝),套餐价随渠道分币种显示,删底部弹窗选方式;点购买直达支付页
- 支付页加订单信息卡(套餐/渠道/金额按币种)
- models:PayChannel + PayCatalogItem.priceUsdtMicro/priceLabel(ch)/perMonthLabel(ch)
订单(P0,全新):
- payment_api.listOrders() + orders_provider(列表 + 详情 family)
- OrdersScreen 列表→详情(状态 pill / 可复制订单号 / 继续支付·重新购买)
- 导航:NavView.orders + desktop_shell 分支 + account 入口
配套:
- l10n +19 getter(支付渠道 4 + 订单 15)×6 语言,l10n 闸绿
- 图标单源迁移 lucide_icons_flutter + pangolin_icons.dart(codegen)
- 联系渠道订正(Telegram/邮件 support@yanmeiai.com/官网) + 前端去广告&时长
- 测试:购买 switch 分币种 + 订单列表/空态/详情 + 假 API listOrders;golden 重录
- flutter analyze 0 error,flutter test 226 全绿
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 00:07:15 +08:00
wangjia
43c76fb978
Merge remote-tracking branch 'origin/main' into feat/pay-v2-integration
...
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 25s
ci-pangolin / Lint — shellcheck (push) Successful in 29s
ci-pangolin / Cleartext Scan — Android 禁明文 (push) Successful in 22s
ci-pangolin / OpenAPI Sync Check (push) Successful in 40s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 19s
ci-pangolin / Flutter — analyze + test (push) Failing after 4m59s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 1m51s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Failing after 1m33s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Failing after 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m59s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (push) Failing after 4s
# Conflicts:
# docs/index.html
# server/cmd/server/main.go
2026-07-11 16:44:05 +08:00
wangjia
262773a9de
fix(client): dispose 不再静默取消支付订单,redirect launchUrl 加失败兜底
...
- PaymentFlowController 新增 stopPolling():只停轮询 Timer,不发远程 cancel、
不改 state;PaymentScreen.dispose() 改调它,不再复用 cancel()——离开支付页
(切左导航 tab)不再等同于放弃订单,cancel() 只留给「取消订单」按钮的显式路径。
- redirect 分支 launchUrl 前加 canLaunchUrl 判定 + try-catch,失败时用
showPangolinToast 给可见提示(新增 l10n openAlipayFailed,zh/en 均补)。
- purchase_page.dart::_choose 下单后严格判 phase == awaitingPayment 才跳转
支付页,建单失败(failed)不再误跳。
- 回归测试:payment_pages_test.dart 补两条用例区分 dispose(不触发 cancel、
state 不归 idle)与「取消订单」按钮(远程 cancel + state 归 idle);
payment_flow_test.dart 补 stopPolling() 单元测试。
2026-07-11 00:06:03 +08:00
wangjia
a5fff28134
feat(client): 购买页三档 + 支付页 render_type 多态(crypto/redirect/qr 预留)+ 导航接线
...
购买页三档 pro(月/季/年,金额来自 GET /v1/pay/catalog)选档 → 选支付方式 → 下单;
支付页按 session.render_type 多态渲染(crypto_address 地址+精确金额+复制、redirect
外链拉起、qr 预留复制兜底),轮询用 Task 6 的 paymentFlowProvider,409
CURRENCY_MISMATCH 走「换方式开新单」。同时收口 paymentFlowProvider 非 autoDispose
带来的轮询生命周期缺口——支付页 dispose 时停轮询(cancel() 延后到微任务执行,避免
在自身 unmount 期间同步改 state 炸 Riverpod 断言)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-10 23:50:35 +08:00
wangjia
e66f79fdf6
feat(client): pay 支付领域模型 + PaymentApi + 支付流控制器(轮询 activated)
...
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-10 23:19:57 +08:00
wangjia
37258e1cb2
fix(client/test): 删未用变量修 flutter analyze(CI Flutter job 全绿)
...
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 24s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 33s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 28s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 10s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 24s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 12s
ci-pangolin / Go — build + test (pull_request) Successful in 7s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 8s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Successful in 4m33s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Successful in 18s
ci-pangolin / OpenAPI Sync Check (pull_request) Failing after 10m21s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 10m33s
stats_device_filter_test.dart 的 const t = StringsZh() 声明后未使用 →
unused_local_variable warning → flutter analyze --no-fatal-infos exit 1 →
CI Flutter job 一直红(既存,与 i18n 修复无关)。删该变量 + 随之无用的
strings_zh import。
容器复验:analyze exit 0(0 warning)· test +97 过 · 覆盖率 38.5%≥28%。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:11:32 +08:00
wangjia
0f05385995
fix(client/test): pin zh locale 修 i18n 默认英文后失配的单测/组件测试
...
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 28s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 37s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 22s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 13s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 29s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 8s
ci-pangolin / Go — build + test (pull_request) Successful in 14s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 9s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 11m2s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 13m15s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 13m22s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 15m0s
早前 i18n 把默认语种改英文(localeProvider 默认 AppLang.en),但这几个测试硬编码
const t = StringsZh() 中文预期,被测代码经 appTextProvider 渲染英文 → 断言失配
(既存 broken,先于 ds-flow;DinD 修复后 Flutter test job 才跑到暴露)。
修:各失败测试的 ProviderScope/Container overrides 加
localeProvider.overrideWith((ref) => _FixedLocale(AppLang.zh)),把被测代码钉回
中文(与硬编码 StringsZh 预期一致)。只动测试文件,未碰 lib/golden/test_config。
- connection_watchdog_test(节点异常提示 / 免费额度归零切断)
- node_connect_confirm_test(点节点/智能卡弹确认取消不切换)
- stats_page_test(PeriodCard 周期卡中文标签)
容器复跑 test/unit+widget+contract:+97 All passed。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 08:41:52 +08:00
wangjia
c4eb92fc2d
feat(ds-flow): Phase 4.2/4.3/4.5 — CJK golden 字体 + timer 泄漏修复 + 基线重生成
...
4.5 harness/资源修复:UpdateNotifier 初始延迟由不可取消的 Future.delayed 改为
可取消 Timer,onDispose 一并取消——修 golden 整屏挂载时 pending-timer 泄漏
(生产也受益,provider dispose 不再悬挂 timer)。
4.2 CJK 测试字体:拷生产子集 NotoSansSC-Regular-subset.otf 进 test/fonts/ +
flutter_test_config 注册(family 'Noto Sans SC'),golden 中文真渲染不出豆腐块。
顺带修 tablet_pages_golden_test 既存编译错(6 语言改动遗留):localeProvider
override 返回值 AppLang → 改返回 LocaleNotifier 子类 _FixedLocale(钉死语言态)。
4.3 权威 Linux 容器(ghcr.io/cirruslabs/flutter)重生成全量 golden 基线:
34 tests All passed,32 张 PNG 更新(CJK 真字 + 暖阴影 + tablet zh/en 矩阵)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 01:27:09 +08:00
wangjia
a5aea9438a
feat(client): 控制面基址默认 https://api.yanmeiai.com + 移除 Android 明文开关
...
TDD: 先加守护测试(断言 kApiBaseUrl 走 https 且不含硬编码 IP),确认失败后
再改 api_config.dart 默认值 + 去掉 AndroidManifest 的 usesCleartextTraffic。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-06 16:37:42 +08:00
wangjia
be27529054
feat(client): 弱网抖动先自动重连当前节点,不再动不动报「节点异常」( #18 )
...
弱网下(实测 RTT 2.2s/丢包)urltest 瞬断或内核 off,看门狗此前一判死就断开+要用户手动
重连(nodeUnhealthyError),体验差。改为:无备用节点时先自动重连**当前节点**(不换节点——
弱网是本地网络问题,换节点无益还会来回横跳),连续失败超过 _kMaxAutoReconnect(3) 次才真报
「节点异常」。urltest 成功或用户主动连接即清零计数,只有持续失败才耗尽。
- _tryAutoReconnectCurrent(bounded 重连当前节点)+ _handleUnexpectedOff(kernel off 走同一逻辑)。
- _onNodeUnhealthy 无备用分支改调 _tryAutoReconnectCurrent;智能有备用仍优先切备用(不变)。
- 重连期给「网络波动,正在重连…」瞬态提示(nodeReconnecting,zh/en)。
- 测试:注入时钟 + 内存 SecureKV(避免 deviceId 挂起);kernel-off 连续判死→3 次自动重连后报
节点异常;路径B 无备用→自动重连当前节点不换节点。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:57:19 +08:00
wangjia
a274cc40c8
fix(client): 额度卡「今日剩余」标签不再被省略 + 短「免费版」标签(1.0.45+46)
...
连接页额度卡头部一行放不下「今日剩余 X 分钟 + 免费版·每日10分钟」时,此前 Flexible
落在标签上,导致「今日剩余」被挤成「今日…」。改为:标签固定绝不省略,数值作最后收缩项;
右上角标签缩短为「免费版」(每日额度由卡片本身「今日剩余 X 分钟」表达,不再挤进胶囊)。
新增 planFreeTag l10n(zh 免费版 / en Free),quotaFree 仍保留供账户页 plan_badge_card。
golden 基线(quota_low/exhausted)在 Linux 权威环境重生成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:40:50 +08:00
wangjia
b461a476a2
feat(client): 免费版连接倒计时 + 到点切断 + 灰按钮 + 看广告加时( #21 前端)
...
四端共享 Dart 实现,配合 #21 后端账户级卡控:
- me.dart: 加 quota_cap_min(当日总额度 = daily + 看广告 bonus,进度条分母)。
- quota_provider: total 取 quotaCapMin;isExhausted;markExhausted(倒计时归零本地置耗尽
+ 登录态拉 me 校准);watchAd() async 调 /ads/unlock(占位 ad_token=uuid)→ 刷新 me,返回 granted。
- connection_provider: 连接时锁定 _freeRemainingSec(会员 null 不倒计时);复用 elapsed 计时器
每 tick 算 freeCountdown,归零 → _onFreeQuotaExhausted(主动切断不报节点异常 + markExhausted);
倒计时用墙上时钟(后台漏跳回前台补上、准时切);连接遇后端 QUOTA_EXHAUSTED 兜底置耗尽。
- connect_button: enabled/onDisabledTap —— 额度耗尽 off 态灰化(锁图标),点击弹加时。
- quota_card: 三态(连接倒计时 mm:ss / 未连接剩余分钟 / 耗尽今日已用完);移动「看广告加时」、桌面「升级会员」。
- ad_reward_dialog(新): 移动端占位广告(播放中→3s→加时→奖励);桌面版硬 10 分钟提示去移动端/升级。
- l10n(zh/en): 倒计时/已用完/看广告加时/占位广告/桌面升级 双语。
- 测试: quota isExhausted/markExhausted;连接倒计时归零自动切断+置耗尽(注入时钟);
额度卡三态 widget;/me 契约含 quota_cap_min;golden 更新(Linux 权威基线 quota_low/exhausted)。
- 文档: docs/free-quota-ad.html + 登记 docs/index.html。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 00:33:21 +08:00
wangjia
58211d2fb8
fix(client): 连接时长按墙上时钟算,切后台/锁屏不漏计
...
iOS 锁屏/切后台后连接页时长少算:elapsed 原来靠每秒 +1 的 Timer 累加,后台
isolate 被挂起、Timer 停跳 → 后台那段时长漏掉。改为记 _connectedAt 起点,
elapsed = now - _connectedAt(墙上时钟),与 timer 是否跳无关;回前台(resumed)
立即 _refreshElapsed 补上。_connectedAt 用 ??= 保留同一会话起点,断开才清。
测试:新增「切后台 300s 时长不漏计」回归(前台 10s + 后台 300s = 310s);全 8 例过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 09:53:49 +08:00
wangjia
37c1724b67
fix(client): 切后台停看门狗 — 后台 urltest 被限流不再误判节点死断连
...
真机(Honor/MagicOS)复现:连上后切后台 ~1min 必断,报「当前节点异常」。
logcat 铁证:进程存活、Dart 看门狗后台照跑,
[Watchdog] urltest stale 59s → upstream dead → unhealthy → doStop
根因:后台(Doze)把 urltest 主动探测限流 → urltest 停更,但 speed 帧仍在流
→ statsLive=true + urltest 陈旧 → 路径A 误判上游死,把正常隧道断掉。上次的
statsLive 闸只挡「stats 整体停」(macOS 睡眠),挡不住「speed 在流、只 urltest 停」。
修:看门狗是前台 UX 功能(发现节点死好让用户换),后台无 UI 可响应、urltest 必被限流
→ AppLifecycleListener 在 paused/hidden 停看门狗,resumed 且仍连接则重启(基准由
_onStats 首帧/gap 重置)。真·内核掉线(路径A',事件驱动)不受影响,仍上报。
测试:connection_watchdog 新增「切后台停看门狗」回归(后台 speed 在流+urltest 停 >45s
不切节点);全 7 例过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 08:54:16 +08:00
wangjia
07861ea04b
fix(client): 看门狗后台唤醒不再误判节点死→假重连 ( #18 )
...
现象:app/Mac 后台或睡眠一段时间再打开,出现"重连过程"但 VPN 没掉。
根因:连通看门狗路径A(urltest stale)用墙上时钟算陈旧度,挂起期 stats 停更、
_lastUrltestOk 冻住;唤醒时一算 >45s 即判上游死 → _onNodeUnhealthy 触发
disconnect+connect(智能模式还自动切节点),但隧道一直好好的。
修(connection_provider.dart 一处):
- _onStats 记 _lastStatsAt(任意 stats 帧时刻);stats 出现 >10s 长间断(挂起/唤醒)
时把 _lastUrltestOk 前移到现在,给节点新的 45s 窗口自证
- 看门狗路径A 增加 statsLive 闸:仅在 stats 确实在流(距今 <10s)且 urltest 停 ≥45s
时才判节点死;stats 整体停(挂起未恢复)则跳过本轮
- 加可注入时钟 _now(默认 DateTime.now),使时间驱动的 path A 可单测
测试:connection_watchdog_test 新增两例 —— ① stats 在流但 urltest 停≥45s 仍正常判死
切节点(path A 未被削弱);② 挂起 60s 唤醒不假重连(无 statsLive 闸则会误切,真回归)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 07:10:34 +08:00
wangjia
00bec95255
chore(client): 清理 tablet golden 旧命名孤儿文件( #11 )
...
删除 5 个旧命名(无 theme/lang 后缀)且无任何测试引用的 tablet golden:
tablet_account/connect/connect_dark/servers/stats.png。当前 tablet_pages_golden_test 的
命名矩阵是 tablet_<view>_{light_zh,light_en,dark_zh}(12 个),孤儿是早期命名残留。
顺手清掉 test/golden/failures/(gitignored 的失败产物)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:47:39 +08:00
wangjia
d9378b6729
fix(client): 看门狗返工 — 意外掉线捕获+服务端down轮询,撤销TUN下假阳性的TCP探测
...
测试暴露:全局TUN下App的socket到节点IP被本地TUN当场接住(假1ms),客户端TCP探数据口
不可行;且节点死时内核先掉线、提示被kernel off覆盖、节点页status不刷新显示绿色。返工:
- 撤销 DataPlaneProber/livePingMs;延迟改回内核urltest(sing-box经REALITY真实直连数据面
探出的RTT,唯一可靠口径),连接态urltest=0→显示—不回退旧ping。
- 看门狗两条可靠路径:A 捕获「非用户主动的内核掉线」→置「节点异常」+刷列表;
B 连接态周期刷/v1/nodes,所连节点被判down→智能切/手动断。
- _offNotice 让显式断开与kernel off读同一提示字段,不再互相覆盖(修「断开但没报错」)。
- 节点页周期15s刷新,让服务端down状态浮现(tile已按isDown置灰)。
返工测试:意外掉线提示/服务端down切/断/健康保持,全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:21:09 +08:00
wangjia
554708a092
fix(client): 看门狗+延迟统一到数据面 IP TCP 探测 + 消费服务端 down 信号
...
修复:数据面挂了客户端无感。原看门狗探 generate_204,会被分流判 direct 走本地
出网假阳性,永不触发。改为三路融合判活,针对「实际所连节点」(_connectedNode,
非会随 ping 漂移的 effectiveNode):
① 客户端→数据口 TCP 握手(节点 host:443,TUN 内必 direct→直奔真实节点,不被分流糊弄);
② 延迟同源:①的 RTT 回填连接页延迟(ConnectionState.livePingMs),挂了掉成 —;
③ 服务端权威:刷 /v1/nodes,所连节点被判 down(dp_healthy=0/agent掉线/运维下线)即处理。
看门狗即时首测 + 周期 15s;连续 3 次握不上 / 服务端 down → 智能切节点 / 手动告警断开。
连接页延迟改读 livePingMs(删除内核 urltest 取数)。补信号③测试。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 21:27:49 +08:00
wangjia
e89f3de748
feat(client): 记住上次选择的节点 + 自动连接 toast(1.0.24)
...
- selectedNodeCodeProvider 改为持久化 StateNotifier(select() 落盘、启动恢复,默认智能 AUTO);
自动连接复用上次选择:智能就智能、具体就具体。
- 自动连接门控加等「选中节点已恢复」(controller.loaded),避免用到未恢复的默认值。
- 自动连接触发时弹 toast「正在自动连接到 X」(全局 ScaffoldMessenger key)。
- 测试:选中节点 select 落盘 + 重启恢复(具体/智能)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:31:01 +08:00
wangjia
8584f0459e
feat(client): 自动连接开关 + Windows 开机自启改任务计划程序
...
- 新增「自动连接」设置开关(默认关,持久化):启动 app 进登录态主界面后,等节点就绪
自动连一次(_RootFlowState 一次性 listenManual nodesProvider → toggle())。全平台生效。
- Windows 开机自启改用 schtasks /rl highest 登录触发任务:本应用 requireAdministrator,
原注册表 Run 键(非提权)开机会弹 UAC/起不来 → 任务计划静默提权拉起。macOS/Linux 仍用
launch_at_startup 登录项。
- 设置页功能组加「自动连接」行;新增 autoConnect/autoConnectSub 文案。
- _load 加 mounted 守卫(异步初始化期间已 dispose 不再写 state)。
- 测试:autoConnect 默认关 / _load 读持久化 / setAutoConnect 落盘。
注:移动端真·常连(Android Always-on + iOS/iPad On-Demand,无 UI 自连)是更大的原生活,
已单列 todo #14 后续做;本轮移动端只有「打开 app 自动连接」。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 17:46:06 +08:00
wangjia
2080062d8c
feat: 数据面故障韧性 — 节点健康上报(控制面) + 客户端连通看门狗
...
盲区:节点 status 只看 agent 的 gRPC 在线,sing-box 数据面坏了(崩/配置坏/数据口不通)
而 agent 仍在线时,节点仍显示 up、connect 放行、客户端「已连接」但流量全失败,且不自愈。
控制面:
- agent 每心跳探 sing-box clash_api /version(2s 超时,连续 2 次失败才报不健康,防抖),
经新增 HeartbeatRequest.data_plane_healthy 上报(手写 agentv1 契约 + proto 同步;JSON codec)。
- NodeLoad 加 DataPlaneHealthy(随 load 写 Redis;字段缺失默认 healthy 防滚动期误杀)。
- effectiveNodeStatus 扩为 (dbStatus, agentOnline, dataPlaneHealthy);ListNodes 与 ConnectNode
统一改用它 → 数据面坏的节点列表置灰 + connect 返回 404 拦截。
客户端(connection_provider 连通看门狗):
- 连上后每 15s 经隧道 HTTP 探海外 generate_204(可注入),连续 3 次失败判当前节点不可用。
- 智能选择 → 自动切到其他最优可用节点重连;手动选定 → 断开并提示「节点异常」(尊重用户选择)。
- 新增 nodeUnhealthySwitched/nodeUnhealthyError 文案。
测试:agent 健康探测防抖、effectiveNodeStatus 真值表、dataPlaneHealthy 助手、看门狗
智能切/手动断/健康不触发;go test ./... 与 flutter test 全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 13:19:22 +08:00
wangjia
6249d5b2ea
refactor(client): 抽公用 PangolinFieldBox 统一输入框 + 根治填充两色接缝
...
根因:主题 inputDecorationTheme 设了 filled:true/fillColor:bg,自定义容器里嵌
TextField 时内层自铺 bg 底,与容器底色(搜索框 bgSubtle / 登录 surface)不一致 →
两端一色中间另一色接缝(节点搜索框最明显,登录/兑换框也有隐患)。
- kBareInput 加 filled:false:内层 TextField 不再自铺底,底色统一交给外层容器。
- 新增 widgets/pangolin_field.dart:PangolinFieldBox(圆角容器 + 底色 + 可选描边/
聚焦光环 + 标签 + 前置图标 + 后置 widget)+ bareInputDecoration() 助手。
- 三处输入框统一改用:节点搜索框、登录/注册字段(_field)、兑换码输入框。
- 重生成 auth_login(dark/light)+ desktop_redeem golden(均人工核对外观正确)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 12:29:53 +08:00
wangjia
ccb7a9bfe7
fix(client): 节点搜索框两色接缝 —— TextField filled:false 让外层 bgSubtle 统一铺底
...
搜索框是双层:外层容器 c.bgSubtle(灰)+ 内层 TextField 继承主题 filled:true/
fillColor:bg(页面底色)。两色不一致 → 图标与左右内边距露出 bgSubtle、中间是 bg,
出现两端灰中间另一色的接缝。kBareInput 当初只压了边框、没动 filled(注释写明为避免
改 golden),遗留此泄漏。搜索框 decoration 加 filled:false,底色统一交给外层容器。
重生成 4 个节点页 golden(desktop/tablet × dark/light/zh/en)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 12:10:32 +08:00
wangjia
ddf8b1d4d8
feat(client): 智能选择卡也走切换确认(防误触)
...
把节点卡的"已连接时切换先弹确认"逻辑抽成 _onSelect(code,name),节点卡与智能
选择卡共用。点智能选择卡:未连接直接选,已连接/连接中弹确认("切换到「智能选择」?")。
node_connect_confirm_test 增"已连接点智能选择卡弹确认→取消不切"用例。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:52:11 +08:00
wangjia
29a6aa9112
fix(client): refresh token single-flight — 修跨平台「放久了/更新后被踢下线」
...
refresh token 是单次使用+轮换(服务端每次刷新 DEL 旧 JTI 发新的)。冷启动/进
页面会并发打多个接口,access(15min)过期后同时多次 401,各自拿同一个旧 refresh
token 去刷新:第一个成功轮换,其余拿已作废的 JTI 必被拒 → 误触发 logout 把用户
踢回登录页。与平台无关(共用 auth 逻辑),表现为「放久了/覆盖更新后要重新登录」。
修法:AuthNotifier.refresh() 用共享 in-flight future(single-flight)合并并发刷新,
旧 token 只被消费一次。新增 auth_refresh_singleflight_test 覆盖并发只刷一次 +
刷新后可再刷。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:05:16 +08:00
wangjia
73da0bc9a2
feat(client): 节点列表防误触 — 已连接时切换节点先弹确认
...
未连接(off)点节点直接选→跳连接页(无破坏性,不弹框);已连接/连接中点其他
节点会断开重连,先弹确认框(标题含目标节点名),取消则维持当前连接。新增
nodeSwitchTitle/Body/Confirm 三条文案 + node_connect_confirm_test 覆盖 off/on 两路。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 10:11:20 +08:00