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
wangjia
6788501e4a
feat(client): 通用自适应弹出菜单(按屏幕位置上/下弹,不挡触发器)
...
新增 AdaptiveMenuButton:GlobalKey 量触发器矩形,据上下剩余空间决定向下/向上
弹出、右对齐裁剪到屏内,Overlay 全屏 barrier + 淡入缩放动画。「我的设备」⋯
菜单由 PopupMenuButton 换成它(重命名 + 强制退出/清除),修正小屏被遮挡问题。
devices_screen_test 同步改用 AdaptiveMenuButton 触发器断言。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 10:11:20 +08:00
wangjia
880fa32793
fix(devices): 本机也显示 ⋯ 菜单(只「重命名」)—— 修单设备无法改名(1.0.14)
...
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 16s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Failing after 14s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 4s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Successful in 9s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 15s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m10s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
之前 ⋯ 菜单对本机隐藏,而用户唯一设备就是本机 → 根本点不到、改不了名。
改:本机显示 ⋯ 菜单但只含「重命名」(自我强制退出/清除无意义,仍只对他设备)。
测试:本机菜单只有重命名断言;全量 flutter 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 09:49:09 +08:00
wangjia
315c343ed5
feat(devices): 设备默认名「平台简写·主机名」+ 支持重命名(1.0.13)
...
ci-pangolin / Lint — shellcheck (push) Successful in 7s
ci-pangolin / OpenAPI Sync Check (push) Successful in 17s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Failing after 14s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 6s
ci-pangolin / Go — build + test (push) Successful in 11s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m3s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
① 默认名:DeviceIdentity 改「平台简写(Win/Mac/Linux/iOS/Andr)·主机名」,主机名截
到 8、总长≈12(替代裸主机名,更短)。
② 重命名:新端点 POST /v1/me/devices/{uuid}/rename(Service.RenameDevice 校验归属+
截 64);store.UpdateName;客户端 account_api.renameDevice + provider.rename +
「我的设备」⋯ 菜单加「重命名」+ 输入弹窗;l10n(zh/en)。
③ 版本 1.0.13。
测试:server RenameDevice(归属/空名/404)+ 客户端 rename widget;全量 go/flutter 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 09:12:36 +08:00
wangjia
8a1db07d19
fix(devices+stats): /v1/me/devices 401 修复 + 设备下拉接已登录设备 + 版本号
...
ci-pangolin / Lint — shellcheck (push) Successful in 9s
ci-pangolin / OpenAPI Sync Check (push) Successful in 19s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Failing after 15s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Successful in 10s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m2s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
① 「我的设备打不开」根因:devices handler 读 devices.CtxKeyUserID,而鉴权中间件
把 user id 写在 auth 的 key 下(类型不同→取不到)→ /v1/me/devices 一直 401。
改 handler 用 auth.UserIDFromContext(与 accountAPI 等一致)。
② 统计页设备下拉改读 devicesProvider(已登录设备),不再只列有用量的设备 →
windows 设备现在会出现。
③ pubspec version 1.0.2+3 → 1.0.11+12,client_version 不再显示陈旧的 v1.0.2。
测试:devices_screen + stats_device_filter widget(下拉读 /v1/me/devices)+全量绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 08:37:11 +08:00
wangjia
e333f59cb3
feat(stats): #10 第②层 —— 统计页按设备过滤(per-device 本地日曲线)
...
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 19s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Failing after 14s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 6s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 11s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m8s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
镜像账户级时区曲线到「按设备」:
- migration 000018 `usage_device_hourly`(每设备 UTC 小时桶,稀疏)。
- ReportUsage deviceID>0 时加 AccumulateDeviceHourly;NodeStore 接口+impl+mock。
- usage.Store.DeviceHourlyRange(JOIN devices 校验归属+解析 uuid→id);UsageCurve
增 deviceUUID 形参:空=账户级,非空=该设备本地日曲线(分桶逻辑复用)。
- /v1/usage?device=<uuid>;客户端 account_api.usage(device)、usageProvider key 改
记录 (days,device)、stats_page 接 statsDeviceProvider → 选设备即重取该设备曲线。
测试:per-device 曲线隔离+归属校验(A的设备不进B)、UsageCurve(deviceUUID)、
migration v18、客户端 widget(选设备→/v1/usage 带 device=)。全量 go/flutter 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 08:04:33 +08:00
wangjia
58526b09e6
feat(client): P6 DevicesScreen 重做 + DevicePlatform 枚举
...
ci-pangolin / Lint — shellcheck (push) Successful in 7s
ci-pangolin / OpenAPI Sync Check (push) Successful in 19s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 7s
ci-pangolin / Flutter — analyze + test (push) Successful in 23s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Successful in 9s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m12s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
回应「状态码用枚举更好」:新增 DevicePlatform 枚举(线格式 fromWire 解析,
UI 只碰枚举,穷尽 switch),Device.platformKind getter;platform 图标按枚举 switch。
DevicesScreen 按 §7 视觉稿重做:行内 名称+「本机」标 / 平台·客户端版本·最后登录 /
在线绿点光晕·离线灰点 + 行尾 ⋯ 菜单(强制退出 / 清除登录信息·危险红)+ 危险二次
确认弹窗;本机行不显示 ⋯(不自我踢)。l10n 新增在线/离线/最后登录/从未登录/强制
退出/清除登录信息/取消 + 确认文案(中英);「当前设备」改「本机」。
新图标 moreVertical/trash/alertTriangle。localDeviceIdProvider 供本机判定。
测试:devices_screen widget 3 例(本机标+在线/离线+版本渲染 / ⋯菜单→强制退出→
确认→POST logout / 清除→DELETE);全量 flutter test 58 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 06:10:37 +08:00