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
wangjia
2f298f0a0a
feat(devices): P2 sessions 表 + 在线/最后登录/客户端版本
...
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
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 4m13s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
migration 000016(mysql+sqlite,含 down):新增 sessions 表(绑 device+refresh JTI)
+ devices 加 client_version/totp_trusted_until。devices 唯一键改 + platform CHECK
加 linux(需 SQLite 表重建)拆出后续迁移,降风险。
后端:新 internal/sessions Store(Create/Rotate/Revoke/RevokeByDevice/
LastLoginByDevice);TokenManager 外露 refresh JTI(IssueWithJTI/RefreshWithJTI/
ParseRefreshJTI);auth.Service 注入 SessionStore——登录建会话、刷新轮换、登出吊销;
DeviceRegistrar 返回 deviceID;ReportUsage 心跳 touch devices.last_seen(在线判定);
devices.ListDevices 经 LastLoginSource 注入返回 online(last_seen<3min)/client_version/
last_login;RegisterIfAbsent 存 client_version。
客户端:Device model 加 online/clientVersion/lastLogin(fromJson 自动解析)。
测试:sessions store 3 例 + ListDevices 在线/最后登录 + device model 2 例 +
migration v16;全量 go test/flutter test 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 00:50:24 +08:00
wangjia
c0c4b94e29
feat(devices): P1 设备注册打通 —— 登录/注册即写 devices 表
...
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 7s
ci-pangolin / Flutter — analyze + test (push) Successful in 26s
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) Failing after 10s
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
后端:auth.Service 加 DeviceMeta + DeviceRegistrar 接口(consumer-side 解耦),
Login/Register 成功签发后 best-effort 注册设备(不强制设备上限,避免免费档重装
churn 锁死用户);handler 加 device 请求体;main 用 authDeviceRegistrar 适配
devices.Service 注入;normalizePlatform 加 linux。
客户端:新 device_identity.dart(SecureKV 接缝 + 稳定 UUIDv4 device_id 持久化 +
名称/平台/版本);弃用硬编码 'mac-001';auth_api login/register + connect 携带
device 元数据。加 uuid + device_info_plus 依赖。
测试:auth 设备注册(触发/best-effort/空 meta) + device_identity(生成/持久/
读失败不重生成/UUIDv4 形态);normalizePlatform linux=true。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 00:28:13 +08:00
wangjia
0542ff027f
feat(client/nodes): 节点页刷新 — 下拉刷新 + 刷新按钮 + 进页刷新 + 10s 限流
...
- nodesProvider.refresh:10s 限流(下拉/按钮/进页多次触发只打一次远端);
不清空当前列表(后台拉、好了再换,失败保留旧数据),避免进页/下拉闪空
- nodes_page:initState 进页自动刷一次;RefreshIndicator 下拉刷新(移动端);
搜索框右侧 _RefreshButton 点击刷新(刷新中 spinner)
servers 页 golden 重生成(搜索行加按钮);CI 闸 golden 未动。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 22:00:26 +08:00
wangjia
8049659660
feat(client): 节点 status=down 在列表置灰+「不可用」+禁选( #7 客户端侧)
...
ci-pangolin / Lint — shellcheck (push) Successful in 8s
ci-pangolin / OpenAPI Sync Check (push) Successful in 24s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
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 18s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Successful in 4m18s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 13s
#7 服务端已在 agent 离线时把节点判 down,但客户端 Node 模型没读 status、
列表照常显示。补:Node.status 字段 + 解析 /v1/nodes 的 status;ServerTile/
_NodeGridTile 在 isDown 时 Opacity 置灰 + 末尾「不可用」+ onTap 禁用。全平台
共享(lib/widgets+screens)。up 节点 Opacity 1.0 无变化,golden 不动。加 down
状态 widget 测试。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 21:35:52 +08:00
wangjia
3785fdf1c1
style(client/shell): 会员卡字体调小,有效期完整显示
...
ci-pangolin / Lint — shellcheck (push) Successful in 5s
ci-pangolin / OpenAPI Sync Check (push) Successful in 21s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 24s
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 16s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Successful in 4m16s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
有效期行被 chevron 挤得截断。套餐名 12→11.5、有效期 10.5→9.5、chevron 15→14、
间距 10→9,让「有效期 YYYY-MM-DD」完整显示。golden 重生成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 21:06:06 +08:00
wangjia
2c5e8785c0
fix(client/shell): 「我的」入口改到底部会员卡(非顶部品牌)
...
按反馈纠正:顶部品牌区改回静态;底部 PlanBadgeCard(套餐/会员卡)做成可点 →
打开「我的」,选中态高亮 + 末尾 chevron。桌面/平板页 golden 重生成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 20:33:30 +08:00
wangjia
b1dd78fddd
feat(client/shell): 侧栏改版 — 我的移到顶部品牌按钮 + 联系放设置下面
...
ci-pangolin / Lint — shellcheck (push) Successful in 5s
ci-pangolin / OpenAPI Sync Check (push) Successful in 20s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Successful in 23s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 6s
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 17s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Successful in 4m16s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
- 顶部品牌区(logo+穿山甲)做成可点按钮 → 打开「我的」,选中态高亮;
从导航列表移除「我的」项(desktop+tablet)
- desktop 导航顺序:…统计 → 设置 → 联系(联系移到设置下面)
桌面/平板页 golden 重生成(CI 闸 components/auth 未动)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 20:24:03 +08:00
wangjia
07f09a1600
feat(client/stats): 两周流量折线图加 Y轴刻度 + X轴日期 + 14个数据点
...
ci-pangolin / Lint — shellcheck (push) Successful in 9s
ci-pangolin / OpenAPI Sync Check (push) Successful in 16s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 5s
ci-pangolin / Flutter — analyze + test (push) Successful in 22s
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 8s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Successful in 4m18s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 13s
之前折线太简单(只一条线+末点)。增强 UsageLineChart:
- Y 轴:3 条网格 + 左侧 GB 刻度(max/mid/0)
- X 轴:隔几天标一个日期(M/D,始终含末点)
- 14 个数据点全画出(surface 光晕+accent 实心,末点加大)
传入每点日期(chartDates)。token 色经参数传入(axis=fg3/surface),零硬编码。
统计页 golden 重生成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 20:12:30 +08:00
wangjia
4f9d2d2cf3
fix(client): 补提交 stats-overhaul 漏掉的 account_api/kernel_process WIP
...
同 device_usage:account_api.dart(deviceUsage/usage 方法)、kernel_process.dart
及对应测试一直未提交,本地有→analyze/test 过,但不在 git→Windows 构建报
「deviceUsage isn't defined」。补齐使提交树自洽可构建。
孤儿组件 device_stat_row/metric_card 已无人引用,不提交。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:53:56 +08:00
wangjia
a7c3795e5a
fix(client): 补提交漏掉的 device_usage 模型 + 测试(Windows 构建缺文件)
...
device_usage.dart 一直是 untracked(stats-overhaul WIP 漏提交),本地有所以
analyze/test 过,但不在 git → bundle → Windows 构建报「找不到 device_usage.dart /
DeviceUsage isn't a type」。account_providers/account_api/golden 测试都引用它,补提交。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:51:25 +08:00
wangjia
01ebafc05e
feat(client/stats): 统计页重设计 — 月/周/日周期卡 + 设备下拉 + 两周折线 + 修刷新
...
布局重做(对照确认稿):
- 3 张周期卡(本月/本周/今日),每张 = ↓下行 · ↑上行 · 使用时长(PeriodCard)
- 右上设备下拉(_DeviceDropdown,先只挂「全部」;每设备归因打通后填设备名,见 TODO #9/#10)
- 柱状图 → 折线图:最近两周(14 天)按天流量(UsageLineChart,CustomPainter 零依赖)
数据源统一 + 修刷新(根治「本月流量冻住」):
- 月/周/日 + 折线全从单一源 usageProvider(30) 聚合(取最后 N 天点求和,末点即今日,
避开 UTC/本地日期坑);不再 me.weekly + usage 两条管线
- usageProvider/deviceUsageProvider 改 autoDispose(进页重取)+ 下拉刷新(invalidate)
+ 刷新连带 meProvider.refresh。后端每 60s 更新,无需更勤
UI 全走 PangolinColors/Text/Spacing token 单源、零硬编码;中英 l10n key 补齐。
widget 数值断言测试(刀1)按新结构重写并通过;统计/连接页 golden 重生成
(非 CI 闸;components/auth 基线未动)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:43:14 +08:00
wangjia
8eb7d04fe1
feat(client): 节点地区图标改国旗(SVG 填满裁切),未知码回退码块
...
CountryCode 由 2 字母码块改为渲染真国旗:country_flags 包按 region 码出 SVG
旗(BoxFit.cover 填满裁切),全平台含 Windows 都显示(emoji 国旗在 Windows
不渲染,故走 SVG)。FlagCode 判定无效码(AUTO/占位/非地区码)时回退原码块;
选中态由 accent 底色改为 accent 边框环。
CountryCode 共享于节点列表/连接页/统计页,故三处节点图标统一变国旗,
对应 tablet_servers/connect/stats + desktop_stats golden 已 Mac 重生成
(这些页非 CI golden 闸;CI 闸的 components/auth 基线未动)。
依赖:country_flags ^3.1.0(解析 3.3.0)。analyze 干净,非 golden 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 16:25:48 +08:00
wangjia
60daa6b399
test(client): 刀1 — 统计页「上屏数值对不对」widget 断言(缺口①)
...
闭合最初诉求「统计准不准」的前端那一半:后端记账已 L4 e2e,前端此前只到
解析单测(解析对 ≠ 渲染对)。新测试把真实形态的后端响应(/v1/me·/v1/usage·
/v1/usage/devices)经真 ApiClient→AccountApi→provider 链(连带测 JSON 解析)
喂给 StatsPage,断言:
- 指标卡:本月流量 ΣgbTotal=3.0、本月时长 Σmin/60=2.0、未连接延迟=节点 ping 42
- 周柱:7 根来自 me.weekly_gb(取唯一值核对)
- 分设备:2 行,每行流量/时长正确
注入 MockClient 不打真网络;用 find.byWidgetPredicate 匹配 MetricCard/DeviceStatRow
公共字段,无需改生产代码。改坏期望值已验证转红(非永真)。自动并入 flutter-client job。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 22:59:05 +08:00
wangjia
eb1bd11439
fix(test): 契约测试解耦 stats-overhaul WIP(CI 抓到的真问题)
...
ci-pangolin / Lint — shellcheck (pull_request) Failing after 9s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 39s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Successful in 2m2s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 2m14s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 31s
CI 暴露:我的契约测试引用了未提交的 stats-overhaul 文件,本地能过(文件在工作区)、
CI checkout 已提交代码就崩:
- api_contract_test.dart import device_usage.dart(?? 未提交)→ flutter analyze 报
'Target of URI doesn't exist'
- usage/contract_test.go 引用 DeviceUsagePoint/deviceUsageResponse(HEAD service.go/
handler.go 均无)→ go-server 编译失败
摘掉 /v1/usage/devices(DeviceUsage)契约,保留 /v1/me + /v1/usage(已提交)。
待 stats-overhaul 合并后再补 DeviceUsage 契约。
验证:flutter analyze test/contract 'No issues';go vet usage 无错。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:13:27 +08:00
wangjia
bb2688cede
test(ci): L3 golden 进 CI(components+auth 子集,Linux 权威基线)
...
L3 视觉回归真进 CI(此前卡 docker daemon 未起 + dirty 纠缠;现启 Docker 落地干净子集):
- 在 ghcr.io/cirruslabs/flutter 容器重生成 components+auth 的 14 张 Linux 权威基线
(auth_login/connect 三态/quota/smart_card;mac 渲染与 CI Linux 不一致,故钉死 Linux),
容器内 --update-goldens 后无 flag 复跑幂等通过
- ci.yml 新增 golden job(第9个):flutter test components_golden_test + auth_redesign_golden_test
- 解耦干净:这俩只 import clean widget;stats-overhaul 的 l10n 改动纯新增(现有键未动)
→ 渲染不受影响、不裹挟 dirty;tablet/desktop-stats golden 仍耦合 stats-overhaul,待合并后并入
- docs/test-architecture.html:L3 状态更新为「已进 CI(子集)」
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 13:20:55 +08:00
wangjia
76dbdcfa83
test(client): L2 点连接能连上 流程 + ConnectApi 注入(支柱1)
...
- connection_provider:加 connectApiFactoryProvider(原内部 new ConnectApi 改成
可注入工厂,支柱1 接缝即接口)——默认行为不变
- test/unit/flow_connect_test.dart:真 toggle → fetchConfig(注入 MockClient 回占位
config)→ bridge.start → statusStream 确认 → on → 再 toggle → off;
验证「非乐观翻转」红线(on 必由 bridge 确认)
- 全量 unit/widget 47 全过(重构不破坏)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:13:40 +08:00
wangjia
95fad37989
test(client): L2 登陆/重启续登/登出 流程(真 AuthNotifier)
...
驱动真实 AuthNotifier(非桩),仅在可注入接缝 TokenStore 处换内存实现,覆盖最初诉求:
- 登陆:saveTokens → isLoggedIn + 令牌持久化
- 重启续登:新 AuthNotifier 从同一 store 恢复登录态(无需重输密码)
- 登出:logout → 未登录 + store 清空,再重启仍未登录
- 3 用例全过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:10:52 +08:00
wangjia
cb6fc25bc9
test(client): L1 数值格式化纯函数单测 + 从 widget 抽出(支柱3)
...
- lib/util/format.dart:抽出 formatSpeed/formatDuration(原私有于 connect_page
_speed / connect_button _fmt,混在 widget 里没法单测)
- connect_page/connect_button 改用公共纯函数(渲染输出不变)
- test/unit/format_test.dart:10 用例覆盖速率 B/s→GB/s 各单位边界 +
时长 HH:MM:SS 补零/小时不封顶/取模 60
- 验证:format 10 + widget 8 全过、analyze 无 error/warning
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:06:34 +08:00
wangjia
64f96fe018
refactor(api): /v1/me 收敛 expire_at → 只发 expires_at(无 web 用户中心)
...
之前后端同时发 expire_at + expires_at(分别给 app + web 用户中心)。现已无 web 端,
双发是纯冗余,统一只用 expires_at:
- account.go:meResponse 删 ExpireAt 字段 + GetMe handler 不再赋值
- client me.dart:fromJson 删 (expires_at ?? expire_at) 别名回退
- 两端契约快照同步:Go meResponse 冻结集去 expire_at(12→11 key)、删 Dart 别名测试
- 验证:go test httpapi 绿、flutter test contract(14)绿、analyze EXIT 0、e2e 全链路绿
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 10:08:50 +08:00