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
wangjia
6bab18a1bf
feat(client): 被强制下线时弹对话框告知原因再登出
...
会话轮询检测到 active=false → 先弹「此设备已被强制下线/需重新登录」对话框(沿用现有
AlertDialog 样式:surface 底/xl 圆角/accent 按钮),用户确认后再 logout 跳登录页,
不再默默跳走。_forcedLogoutHandling 防重复弹。新增 l10n sessionRevoked{Title,Body,Ok}。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:33:43 +08:00
wangjia
0b33f12400
feat: 近实时远程下线 — 客户端轮询会话有效性(~15s),服务端 GET /v1/me/session
...
控制面无推送通道,access token 是无状态 JWT(15min),强制退出后被踢设备要等 token 过期
(≤15min)才登出。改成客户端每 15s 轮询会话是否仍有效,被强制退出即登出 → 延迟压到 ~15s。
- 服务端:sessions.HasActiveSession(user,device) + devices.SessionActive(按 UUID,fail-open)
+ GET /v1/me/session?device_id= 返回 {active}(恒 200,判据在 body)。无新迁移。
- 客户端:account_api.sessionActive + main.dart _RootFlowState 15s 轮询,active=false 即 logout
(网络/鉴权异常不据此登出,fail-safe)。
- 测试:TestSQLite_SessionHasActiveSession(建会话=活跃→RevokeByDevice→非活跃)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:25:16 +08:00
wangjia
7f070a0693
fix(client): 看门狗 path A 仅在「连上(on)后掉线」触发,排除 connecting 握手瞬态
...
macOS NE 连接序列是 off→connecting→off→connecting→on,connecting 期有瞬态 off。原 path A
条件 wasActive 含 connecting → 一连接就在握手瞬态 off 上误报「节点异常」(实际连上了)。
改:仅 phase==on(真正连上过)再非用户主动掉线才判节点异常;connecting→off(握手抖动/
连接失败)不触发。跨端修复(桌面也更稳)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 07:01:13 +08:00
wangjia
b99d6d71ae
fix(client): 修 _disposed 潜伏 bug + build 只 watch isLoggedIn,根治延迟冻成 —
...
根因:1.0.36 的 401 自愈触发 auth token 续期 → authProvider 状态变 → nodesProvider.build()
重跑 → Riverpod 先调上一轮 ref.onDispose(()=>_disposed=true),新 build 没复位 → _disposed
永久 true → setLivePing/_measure 开头的 if(_disposed)return 让 urltest 回写/延迟实测全部静默
→ 延迟冻在 —。修:① build 开头 _disposed=false 复位;② build 改 watch authProvider.select
((s)=>s.isLoggedIn),token 续期(accessToken 变但仍登录)不再重建 nodesProvider、不清延迟。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:32:15 +08:00
wangjia
15133b8a72
feat(client/macos): 开机自启隐藏到托盘(--autostart),与 Windows 对齐
...
macOS 登录项(launch_at_startup)注册时带 args:['--autostart'];main() 读该参数
windowManager.hide();MainFlutterWindow.awakeFromNib 检测到 --autostart 时 orderOut
兜底(避免 storyboard Visible At Launch 闪窗)。常规启动无参数,正常显示窗口。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:27:30 +08:00
wangjia
a03de6c06a
fix(client): 节点拉取 401 自愈+失败不清空列表;看门狗 path A 跨端(urltest 陈旧判活)
...
bug:周期刷新拿到 401(token 过期)时 _fetchNodes return const [] → 把节点列表清空,
用户「看不到节点」误以为 server 挂。修:① 非 200 抛异常,refresh 失败保留旧列表(绝不清空);
② 401 用 refresh token 续期重试一次自愈,续期失败才登出。
看门狗 path A 全平台统一:不再只靠原生报 off/error(libbox/NE 行为不一),改用内核 urltest
陈旧度(经 proxy 探测,四端都有 stats)——urltest 超 45s 未成功即判上游死 → _onNodeUnhealthy。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 01:23:13 +08:00
wangjia
795082066e
fix(client): 刷新节点列表时保留已有 ping,修延迟被 refresh 清成 — 的根因
...
日志实证:urltest 回写正常(best=561ms→setLivePing),但看门狗/节点页每15s refresh()
重拉服务端列表、解析时 ping=0(占位),state=result 把刚写进去的延迟清零 → 显示 —,
下一帧 setLivePing 又写回 → 来回闪。修:refresh 用 _mergePings 按 uuid 保留已有 ping
(ping 是客户端实测,服务端列表不带)。去掉诊断日志。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 00:50:58 +08:00
wangjia
a06bfda45c
diag(client): _onStats 加 urltest 诊断日志,定位延迟显示 — 根因
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:50:48 +08:00
wangjia
16af1c9f02
fix(client): 连接态把内核 urltest 回写连接节点 ping,修延迟显示 — + 保持同源一致
...
1.0.32 纯 direct node.ping 的代价:auto-connect 太快连上、没有断开窗口让 direct TCP 量到
延迟 → node.ping 一直 0 → 显示 —。修:ConnectionController 订阅 stats,连接态把内核 urltest
最小正延迟回写连接节点的 node.ping(setLivePing)。连接页与节点列表都读 node.ping → 同一个数;
连接节点有实时 urltest 值、其余节点保留 direct 实测。断开态仍 direct TCP。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:35:40 +08:00
wangjia
0736014552
fix(client): 延迟统一用 node.ping(client→数据面 RTT),连接页与节点列表同源一致
...
撤掉连接页连接态的内核 urltest —— 它与节点列表的 node.ping 是两套口径、两处显示不一致。
统一全部用 node.ping(直接 TCP 握手 节点:443),连接页显示生效节点的 ping、节点列表显示
各节点 ping,永远同一个数。连接态 TUN 测不到新值则保留上次实测(_measure 已改保留)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:28:39 +08:00
wangjia
d08b908e1f
fix(client): 节点列表延迟探测失败时保留旧值,修连接后整列延迟消失
...
返工引入的回归:看门狗连接态周期 refresh→_measure 重测所有节点 TCP ping,但连接态
全局 TUN 把到节点的握手接住→probeAll 返回 0→pings[uuid]??n.ping 因 0 非 null 覆盖成空。
改:探测返回 0 时保留旧 ping。连接态保留上次延迟,断开态正常刷新。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:26:19 +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
49bef2fb27
feat(client): 开机自启拉起直接最小化到托盘(--autostart 标志)(1.0.28)
...
计划任务 /tr 命令带 --autostart;main() 检测到该参数在首帧前 windowManager.hide()
隐藏主窗口到托盘,自动连接逻辑照常跑。手动打开(无参数)仍正常显示窗口。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 20:11:36 +08:00
wangjia
ada99e6c3a
fix(client): 自动连接死锁兜底(轮询)+ 逐次门控日志(1.0.27)
...
复现:armed→设置已加载后无 ready。死锁:selLoaded 靠 selectedNode controller 的 loaded 标志,
而该标志完成时若选中值未变(==默认)不会发通知,若 nodeSub 已先于它触发则此后无人再调 tryGo。
修:_maybeAutoConnect 加 Timer.periodic(400ms,≤12s)兜底复查,decided 后停;并恢复每次 tryGo
打 check 日志(settingsLoaded/selLoaded/node uuid)以确认卡点。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:58:53 +08:00
wangjia
277d1c6e97
feat(client): toast 限定主显示区/自适应宽/上限80%/超长多行居中(1.0.26)
...
按真相源新规:toast 改为自定义 Overlay 实现(widgets/pangolin_toast.dart),完全控制布局——
不覆盖左侧栏(left=侧栏宽 desktop204/tablet232/mobile0)、宽度自适应内容上限主区80%、超长文本
多行、主区内水平居中贴底、淡入轻起;仍走语义 token 明暗适配。自动连接改用 showPangolinToast。
同步 design/CONTRACT.md §2 toast 规格(canonical=showPangolinToast)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:50:33 +08:00
wangjia
828bb4ca94
style(client): toast 跟随明暗主题(1.0.25)
...
真相源/主题里原无 toast 定义,SnackBar 用 Material 默认黑底,明亮主题下突兀。
PangolinTheme 加 snackBarTheme:surface 底 + fg1 字 + 描边圆角(与卡片同风格)、floating,
浅色得浅 toast、深色得深 toast。自动连接的 toast 随之适配。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:39:00 +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
f2891ff2f9
fix(client): 自动连接门控改判生效节点就绪 + _connect 诊断日志(1.0.23)
...
自动连接已正确触发,但 _connect 撞 node.uuid.isEmpty 报「节点尚未就绪」:门控用的是
nodes.any(uuid),而 _connect 用 effectiveNode。改门控直接判 effectiveNode.uuid 非空(与
_connect 同一判据)。_connect 加一行日志记录 node/uuid/selected/nodes 便于确认。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:15:39 +08:00