wangjia
73012ee03c
feat(client/orders): 订单列表/详情显示 expired 过期态
...
- _statusPill 函数加 'expired' => neutral 灰状态胶囊(复用 canceled 视觉档)
- actions() 函数让 expired 单复用 created 分支,显示「继续支付」按钮
- 对接后端 Task 0 返回的 status=='expired' + Task 2 的 t.orderStatusExpired
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 18:28:28 +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
44f22dcf52
fix(client/notif): 通知占位示例文案改英文(默认语言一致,避免英文 app 显中文)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 18:05:24 +08:00
wangjia
f5669c1a67
feat(notif): 通知占位屏 + 移动顶栏铃铛(Spec ③接口占位)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 18:01:07 +08:00
wangjia
2d37ef1e7b
fix(client): NotificationBell.onTap 恢复 required 非空(对齐规范)
...
- notification_bell.dart: VoidCallback? → VoidCallback(字段非空)
- content_top_bar.dart: 在 null-guard 内用 onBell! 传给 NotificationBell
- flutter analyze: No issues found
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:57:02 +08:00
wangjia
df19629944
feat(shell): 桌面侧栏加 Account 一级项 + 顶栏通知铃铛 + invite/notifications 视图接线
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:51:48 +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
c520449002
refactor(contact): 渠道抽单源常量 + RedeemScreen 删去哪买引流卡(仅留兑换码)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:30:25 +08:00
wangjia
3a270cbeb0
feat(l10n): 邀请/通知/设置分组文案 ×6 语言(Spec ②③占位)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:25:30 +08:00
wangjia
c48ff034e2
feat(nav): NavView 加 invite/notifications + kAccountSubViews 收录 invite
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-07-12 17:21:30 +08:00
wangjia
6f5a805536
design(prototype): 渲染新 IA 视觉稿(桌面+移动)— 含 bell 图标单源 + 通知/邀请预留
...
- icons.js 加 bell(codegen→pangolin_icons.dart,L1 同集 59)
- 侧栏提 Account 为一级/顶栏通知铃铛/Account 重排(购买&订单&兑换 + 邀请好友)
- Settings 归三组(连接/外观/关于)+ Web 链接降级;Contact 单源;兑换码仅码
- 通知收件箱 + 邀请好友 占位屏(细节各自 Spec ②③)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 09:08:25 +08:00
wangjia
48afe86475
fix(client): 设置页购买入口改为显示套餐(PurchaseScreen)而非兑换/去哪买链接
...
- 「购买套餐」→ PurchaseScreen(渠道 switch + 三档套餐 + 下单),不再进 RedeemScreen 的引流卡
- bump 1.0.69+70
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 07:24:18 +08:00
wangjia
a164490001
fix(client): 设置页兑换入口空 onTap 修复 + 新增我的订单入口(桌面侧栏 Settings 路径)
...
- settings_page 的「兑换&购买」行 onTap 之前是空占位 → 接上 open(redeem)
- 新增「我的订单」行 → open(orders)
- bump 1.0.68+69
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-12 07:15:37 +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
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
bb5acb6c1e
feat(ds-flow): Phase 4.1 — 清 adaptive_menu 唯一裸 Material 色
...
adaptive_menu.dart shadowColor: Colors.black26(冷黑)→ PangolinColors.sand950
withValues(0.24)(暖近黑,对齐 §5 暖阴影铁律)。UI 层(widgets/screens/shell)
自此零裸 Material 色。flutter analyze 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 01:11:11 +08:00
wangjia
805bf8e2ca
feat(ds-flow): Phase 1.1-1.2 — 原型单源目录 + tokens.css 迁为真源
...
- design/prototype/serve.mjs:零依赖热重载预览服务器(照搬 jiu,check-ds
存在性守护,Phase 5 前静默跳过)
- design/prototype/tokens.css:token 真源(现结构已满足 ds-flow 的
base :root + [data-theme=dark],字节迁移保证 codegen 零 diff)
- design/colors_and_type.css:退化为薄 @import 别名(供历史 preview/*.html
浏览器内引用;codegen 不再读它)
- 三个 codegen(Flutter gen_flutter_tokens / website+usercenter build-tokens)
+ drift 检查全部重指向 prototype/tokens.css
- 生成产物 diff 仅头注释源路径行,token 数值零变化(已验证)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 00:00:38 +08:00
wangjia
0b86138b55
feat(client/update): 更新提示改 jiu 式顶部横条(非强制不再弹窗)
...
Deploy Client / build-windows (push) Successful in 1m47s
Deploy Client / build-android (push) Has been cancelled
Deploy Client / build-macos (push) Has been cancelled
Deploy Client / build-ios (push) Has been cancelled
Deploy Client / release-deploy (push) Has been cancelled
按需求去掉居中弹窗,改成不打断的顶部 banner(对照 jiu 黄条):
- 非强制更新 → home_shell 顶部 UpdateBanner「发现新版本 vX + 立即更新 + ×」;
「立即更新」App 内下载安装,「×」忽略此版本(banner 隐藏,更新版本会重现)
- 强制更新(force_update)→ 仍走不可关闭弹窗 showUpdateDialog
- 忽略状态改 dismissedUpdateVersionProvider(按版本号,响应式);去掉 notifier
的 _dismissed
- 设置页「检查更新」有更新时:清忽略版本→banner 显示 + toast,不再弹窗
- 新增 6 语「立即更新」文案。flutter analyze 仅 2 个既有 withOpacity info
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 19:19:05 +08:00
wangjia
4940278ea5
feat(migrate): 用户中心迁到 pangolin.yanmeiai.com/user/ + 域名配置化
...
Deploy Server / deploy-server (push) Successful in 2m55s
Deploy Site / deploy-site (push) Successful in 2m41s
Deploy Client / build-windows (push) Successful in 1m47s
Deploy Client / build-android (push) Successful in 7m39s
Deploy Client / build-macos (push) Successful in 3m40s
Deploy Client / build-ios (push) Successful in 4m48s
Deploy Client / release-deploy (push) Successful in 2m25s
原独立子域 app.yanmeiai.com → 主站子路径 /user/(用户选停用旧域名):
- 域名配置化(去硬编码):客户端 kWebUserCenterBaseUrl 收进 api_config.dart
(dart-define 可覆盖);官网 site.ts、服务端 CORS_ORIGINS 本就是配置
- 用户中心 next.config basePath=/user;layout.tsx 的 /colors_and_type.css 手动
拼 basePath(public 根绝对资源不自动加前缀,否则 404)
- CI 合并部署:compile-site + compile-usercenter + combine-site(用户中心并入
dist/user/ + _headers 按 /user/* 分域:官网严格 CSP,用户中心 unsafe-inline+
connect-src https)→ 单次部署 pangolin-site;删独立 pangolin-usercenter 部署
- 服务端 CORS 默认 origin app.yanmeiai.com → pangolin.yanmeiai.com(+ 测试)
- 客户端 web_launch 走新址 → 随 client-v1.0.62;go test CORS 过、flutter analyze 净
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 17:33:22 +08:00
wangjia
c723bdd53e
feat(client/update): 启动自动检查 + App 内下载安装(不走浏览器)
...
Deploy Client / build-windows (push) Successful in 2m2s
Deploy Client / build-android (push) Successful in 2m13s
Deploy Client / build-macos (push) Successful in 3m45s
Deploy Client / build-ios (push) Successful in 3m58s
Deploy Client / release-deploy (push) Successful in 2m1s
参照 jiu 补齐,但比 jiu 多做了 Android 原生安装(jiu 的 App 内下载仅 Win/mac):
- 启动自动检查:update_provider 改 AsyncNotifier,build() 延迟 3s 首查 + 每小时
轮询;home_shell watch 拉起、listen 弹更新框;_dismissed 防反复弹(轮询重置);
强制更新走不可关闭弹窗。设置页「检查更新」改 forceCheck()。
- App 内下载(core/update/app_updater.dart,http 流式带进度框,不再 launchUrl):
· Android:下 APK → open_filex 拉起系统安装器(+ REQUEST_INSTALL_PACKAGES)
· Windows:下 exe → Process.start(detached) → exit(0) 覆盖安装
· macOS :下 zip → open 解压 + 访达显示,提示手动拖入(带系统扩展不自动 swap)
· iOS :降级外链(TestFlight/App Store)
失败降级浏览器下载。新增 6 语更新进度/失败/提示文案。
- 依赖 open_filex;flutter analyze 仅 2 个既有 withOpacity info。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 17:09:29 +08:00
wangjia
617b43083d
feat(client/i18n): B1 硬编码文案 6 语化 + B2 语言持久化
...
B2 持久化:localeProvider 由 StateProvider 改 StateNotifier(LocaleNotifier),
默认英文,选择存 shared_preferences(key pg_lang),重启保留;新增 appTextFor(lang)
helper 供非 Consumer 场景复用。两处切换 .state= 改 .set()。
B1 硬编码 UI 文案 6 语化(AppLangMisc 扩展,exhaustive switch 不会漏):
- 不可用 / 流媒体优化 / 节点未就绪 / 连接失败 / 加载失败 / 暂无设备 / 改邮箱
- 相对时间(刚刚/X分钟前/…)relativeTime(Duration)
- 修 auth_screen 错误消息 bug:原 _errorZh 永远显示中文(连英文用户都中招)→
按当前语言取 messageZh/En(非中文显英文)
仍回退英文(服务端数据,需服务端多语字段):节点/套餐名(nameZh/nameEn)、
API 错误消息(messageZh/En)。flutter analyze 仅 2 个既有 withOpacity info。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 14:32:13 +08:00
wangjia
3aeb2a8680
feat(client/i18n): 新增 日/韩/俄/西 4 语,默认语种改英文
...
- AppLang enum 扩为 zh/en/ja/ko/ru/es;新增 strings_{ja,ko,ru,es}.dart
(各 161 条,照 strings_en.dart 结构逐条翻译,无红线词,scan-redline 过)
- app_providers: localeProvider 默认 AppLang.en(国际化默认);appTextProvider
三元改 switch 全覆盖 6 语
- AppLang.nativeLabel 扩展(各语言本地名);settings/account 两处 _LangSwitch
段控改下拉(6 语横排会挤)
- flutter analyze lib 仅剩 2 个既有 withOpacity deprecation info
注:节点/套餐名、错误消息等 ~11 处数据驱动的 zh/en 三元暂回退 en(非崩溃,
后续可扩多语字段)。localeProvider 暂无持久化(沿用原设计,后续可加)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 13:20:16 +08:00
wangjia
6277e86d0f
feat(client): SSO 用户中心按钮 + 自动更新检查(镜像 jiu)
...
Deploy Server / deploy-server (push) Successful in 2m56s
SSO: web_launch.openWebUserCenter → POST /v1/auth/web-ticket → 打开 app.yanmeiai.com/sso?t=…;
设置页加「用户中心(网页)」入口。自动更新: update_provider 查 /version 语义比对 + update_dialog
「发现新版本」;设置页「检查更新」由空 onTap 接上。加 url_launcher(原缺)。flutter analyze 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 00:13:50 +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
f035552ff5
fix: disconnect 吊销每设备凭证 + 客户端断开时真正调用(F4,#28)
...
审查 F4 两层问题:①DisconnectNode 只撤账户级 ent.DpUUID,而 connect 下发的是
每设备 devDp——吊销从未对准目标;②客户端从未调用过 disconnect 端点(只有
fetchConfig),端点是死代码,凭证一律活到 TTL(付费 24h)。
- server:disconnect 收 optional body {device_id},吊销该设备 dp_uuid(优先)+
账户级遗留兜底;旧客户端无 body 走兜底,行为不回归。nil hub 守卫(测试友好,
与 ListNodes 一致)。
- client:ConnectApi.disconnect(best-effort,5s 超时吞错);_disconnect 加
revokeCredential 参数,仅在「不会紧接重连同节点」的路径置 true(用户主动断开/
额度耗尽/登出)——看门狗断开→重连若也吊销,revoke 可能晚于新 connect 推送、
误杀新会话。
- test:httpapi disconnect 三态(带 device_id 双吊销/无 body 仅兜底/设备已移除
不炸);client 功能套件 182 过(golden 为已知 macOS 本地漂移,不相关)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:29:01 +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
837bf55f6a
fix(client): 「今日剩余」不再冻结 —— 倒计时兜底启动 + 会话后静默刷新 /me(1.0.46+47)
...
免费版「今日剩余 xx 分钟」一直不变的两处原因:
1. 倒计时仅在经 _connect 连接时锁定 _freeRemainingSec;Android 常驻隧道/重启后自动
同步到已在跑的隧道时不走 _connect → 倒计时不启,连接页只显示静态「今日剩余」。
现于 _onKernelStatus.on 兜底:免费且未锁定则按当前额度补锁,保证显示会动的倒计时。
2. /me 仅登录时拉一次,会话消耗后不刷新 → 「今日剩余」停在登录值。现于断开/内核掉线
后静默刷新 /me(新增 MeNotifier.silentRefresh,不进 AsyncLoading → 不闪整页 spinner)。
markExhausted / watchAd 也改用 silentRefresh,避免额度变动时连接页闪 spinner。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 01:54:53 +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
0a70a24cdb
feat: 连接设备上限 backstop — 兜住"已登录的超限设备"( #16 )
...
ci-pangolin / Lint — shellcheck (push) Has been cancelled
ci-pangolin / OpenAPI Sync Check (push) Has been cancelled
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Has been cancelled
ci-pangolin / Flutter — analyze + test (push) Has been cancelled
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Has been cancelled
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Has been cancelled
ci-pangolin / Go — build + test (push) Has been cancelled
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Has been cancelled
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Has been cancelled
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Has been cancelled
登录挡板只拦新登录,已登录的超限会话(如 pro 已连 5 台)永远不被提示 → 限制形同虚设。
补服务端连接卡点:超限账户连接直接拒,兜住已登录设备(下次一连即被拦,无需重登)。
服务端:
- Entitlement 加 MaxDevices(EntitlementForUser 查 p.max_devices;free 默认 1)
- NodeStore.CountActiveDevices(近 30d 活跃)+ SQLNodeStore/mock 实现
- ConnectNode:activeCount > MaxDevices → 403 {code:DEVICE_LIMIT_EXCEEDED, max_devices}
客户端:
- ConnectApiException 解析 code/max_devices(结构化错误体)
- _connect 遇 DEVICE_LIMIT_EXCEEDED → 置 deviceLimitProvider → 顶层路由弹「移除设备」页
(设备列表走 devicesProvider),移除到上限内自动放行
后端 go test(store/nodes/httpapi/devices)全过;前端 analyze 干净 + 66 测试过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 22:11:13 +08:00
wangjia
34d980d875
feat(client): 设备数超限挡板 DeviceLimitScreen( #16 前端)
...
登录响应带 device_limit 时,顶层路由把用户挡在「移除设备」页,降到上限内才进主界面。
- auth_api.dart:AuthTokens 解析 device_limit;新增 DeviceLimit/DeviceBrief 模型
- auth_provider.dart:deviceLimitProvider(挡板状态);auth_screen 登录后置入
- main.dart:isLoggedIn 后若 deviceLimit!=null → DeviceLimitScreen(挡 HomeShell)
- screens/device_limit_screen.dart(新):设备列表(本机标注不可删)+ 每台移除
+ 一键「移除最久未用」+ 退出登录;降到上限内自动清挡板放行(复用 devicesProvider.remove)
- l10n:deviceLimitTitle/Desc/RemoveOldest(zh/en);顺带修 devicesSub 文案 5→3 台
analyze 干净;client 全量测试 161 通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-01 19:25:14 +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
d0d39c93f6
fix(client): 设备列表离线时间改用 last_seen「最后在线」(原 last_login 不准)
...
设备列表对离线设备显示「最后登录 X 前」,取的是 last_login(会话创建那一刻)。一直在线
的设备断开后会错显成登录时间(如 13 小时前),与实际「最后活跃」不符。改用 last_seen
(连接/用量上报/~15s 会话轮询都会刷新),标签相应改为「最后在线」/「Last online」。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 22:30:43 +08:00
wangjia
328d34a320
fix(client): 「我的」页加「设置」入口(我的设备下面)——iPad 竖屏/手机可达设置
...
iPad 竖屏回落 mobile 底 Tab(无侧栏),设置入口够不着。在账户页「我的设备」下面加一行
「设置」→ 下钻 SettingsScreen(复用桌面 SettingsPage + 移动返回栏)。与「联系我们」一致:
desktop/tablet 侧栏 + 账户页都可进。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 21:48:54 +08:00
wangjia
849e30dd05
fix(client): iPad(tablet)对齐桌面——侧栏补设置/联系我们 + 顶部避让状态栏
...
iPad 测试反馈:① 平板侧栏只有 连接/节点/统计,缺「设置」「联系我们」(桌面侧栏有,只能从
「我的」页进);② 顶部 brand/标题与 iOS 状态栏重叠(TabletShell 没包 SafeArea)。
- TabletShell:侧栏补 设置/联系我们 两项 + content 路由到 SettingsPage/ContactPage(对齐
DesktopShell);整体包 SafeArea(bottom:false)避开状态栏/notch。
- SettingsPage:开机自启是桌面登录项概念,iOS/Android 无此能力 → 仅 desktop 平台显示该行。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 21:36:33 +08:00
wangjia
7add0259b4
chore(client/macos): 清理延迟排查的诊断打点 + debug 注入(最终干净版)
...
延迟显示已修通。移除排查期的临时诊断:扩展侧 Log 客户端/klog 捕获、log.level=debug 注入、
diag 字段/setDiag;主 app StatsClient 每5帧的 dl/ul/urltest 打印;Dart 侧 [Stats]/[Ping] 打点。
保留全部修复:clash_api 注入取 urltest、sendProviderMessage 统计通道、/proxies history+active
缓存、共享广播流、_onStats 回退 effectiveNode、连接态跳过直连实测。
CURRENT_PROJECT_VERSION 52→53。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 18:03:30 +08:00
wangjia
e00163e2d9
fix(client): statsStream/statusStream 缓存为共享广播流,修延迟不回写
...
根因:VpnNativeBridge.statsStream getter 每次访问都 receiveBroadcastStream(),多订阅者
(连接页速度 + ConnectionController._onStats 回写 urltest 延迟)各起一条原生订阅;但
EventChannel 原生侧只留一个 sink(StatsStreamHandler.onListen 覆盖),后订阅者赢、先订阅者
变哑。速度订阅赢了 sink → _onStats 收不到 → urltest 实测(已确认到达 native:reality-out=595)
永不回写 node.ping → 连接页延迟一直 —。
修复:receiveBroadcastStream() 只调一次、缓存成 asBroadcastStream() 共享流,所有 Dart
订阅者复用同一条原生订阅。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:57:37 +08:00
wangjia
e6e7d5db10
diag(client): _onStats 顶部无条件打点 + 订阅打点,确认是否被调用
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:47:16 +08:00
wangjia
d14f7a0ec5
diag(client): _onStats/setLivePing 打点,定位延迟为何不回写 node.ping
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:26:56 +08:00
wangjia
dbe657f997
fix(client): 自动连接到已运行隧道时延迟显示 — —— _onStats 回退 effectiveNode
...
连接页延迟一直 —:自动连接/重连到「已在跑的隧道」时不走 _connect → _connectedNode 为
null → _onStats 提前 return,内核 urltest 实测(已确认到达 app:reality-out=579)无处回写
node.ping。与看门狗 _checkHealth/_probe 一致,回退到 effectiveNode(当前/智能所选节点)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 17:17:57 +08:00
wangjia
6809e0a377
fix(client): 连接态延迟只认内核 urltest,跳过直连实测(对齐 Windows 方法)
...
连接页延迟显示 ~3ms:连接态下 _measure 的直连 TCP 实测被全局 TUN 在本地接住,
connect() 几毫秒即返回(假值),覆盖了内核 urltest 经 setLivePing 回写的真实 RTT
(macOS system 栈 TUN 尤其明显)。Windows 端延迟本就取 sing-box urltest(clash API
/proxies 的 history delay),macOS 经 libbox writeGroups 的 item.urlTestDelay 同源。
修复:_measure 在 connectionProvider.phase==on 时直接返回,不做直连实测——连接态延迟
只认 urltest(与 Windows 一致),断开后再恢复直连实测。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 12:18:32 +08:00
wangjia
8680131b3d
fix(client): 登出(主动/被强退/会话过期)一律断开隧道
...
bug:logout() 只清 token,不碰隧道 → 被强制下线/正常退出后 VPN 还连着、继续用数据面。
修:ConnectionController 监听 authProvider,isLoggedIn 由 true→false 时断开隧道(_disconnect)。
一处覆盖所有登出路径(设置页登出/会话轮询强退/token 续期失败登出);token 续期仍登录不触发。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:00:00 +08:00
wangjia
d192d96d95
fix(client): 设备页每 15s 静默刷新列表,其他设备状态变化 ~15s 内可见
...
原设备列表打开时加载一次就不动(感觉十几分钟才同步)。加透传式轮询包装器
_DevicesAutoRefresh:在设备页期间每 15s 调 DevicesNotifier.refresh()(静默、不闪 loading、
失败保留旧列表)。配合服务端会话轮询刷 last_seen + 90s 窗口,其他设备上/下线 ~15s 内反映。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 08:05:32 +08:00