Commit Graph

16 Commits

Author SHA1 Message Date
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 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 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 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 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 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 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 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 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 4886416e8b feat(client): settings 完整版(开关组) + 联系/设置整页 golden
- settings_page 补全对照 dapp.jsx DSettings: 功能开关组(开机自启/智能分流/
  Kill Switch, 本地演示态) + 配置组补检查更新行; 改 ConsumerStatefulWidget 持开关态
- l10n 新增 autostart/smartRoute/killSwitch(+Sub) + checkUpdate(中英)
- desktop 整页 golden 补 联系页 + 设置页(凑齐可截 5 页: 节点/统计/账户/联系/设置)

测试: flutter test 81 通过(+2 golden), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:22:28 +08:00
wangjia 4dc4127252 feat(client): 三端布局架构 + macOS 桌面端 + app 图标
三端布局(mobile/tablet/desktop):
- core/responsive/form_factor.dart 形态判定 + shell/ 分发器(home_shell→desktop/mobile)
- desktop_shell 对照 ui_kits/desktop/dapp.jsx: 侧栏204·6项 + 套餐卡 + 顶栏(标题/状态/主题切换) + 连接页居中单列
- 新增组件 nav_sidebar / plan_badge_card / content_top_bar / bottom_tab_bar
- 新增一级页 contact_page / settings_page; navigation_provider(NavView)
- 删除旧 widgets/home_shell.dart(逻辑迁入 shell/)

macOS 桌面端:
- 窗口默认 920×600 + 最小 720×560(MainFlutterWindow.swift)
- app 图标替换为穿山甲(AppIcon.appiconset 全套, 由 app-icon.svg 渲染)

其余(本会话):
- Phase2 接线: auth_api/token_store/auth_provider/vpn_bridge_provider + 真实 connection/nodes
- lucide_icons 兼容补丁(packages/lucide_icons_patched) 修复 IconData final 报错
- 测试修复: connect_passthrough(UTF-8) / harness / golden @Skip
- l10n 新增 settingsTitle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:23:20 +08:00
wangjia f97ae8186b feat(client): Flutter 逐屏还原 + iPad ≥900 断点 (tsk_gpPXi-icyeOE)
对照 design/ui_kits mobile+tablet 原型逐屏补齐 Flutter 客户端:

- l10n 资源层(strings_zh/en,单显)+ Riverpod 状态层(连接状态机/免费额度/
  节点选择/语言/主题),UI 与数据解耦,mock 数据接 API 不动 UI。
- 连接键严格三态(off 虚线轨道环 / connecting 旋转弧 / on 满环+计时+光晕),
  状态来自 connectionProvider,点击只派发事件——禁止乐观显示。
- 节点页置顶「智能选择」推荐卡(clay 渐变 zap + 推荐胶囊,默认选中);
  免费额度卡(剩余分钟+进度条 ≤3 分钟切 warning + 看广告解锁变绿)。
- Tab 左右滑动切换(手势竞技场仲裁,子页滚动不误触发,200ms 方向感知滑入)。
- iPad/宽屏 ≥900 LayoutBuilder 切侧栏分栏(导航行高 ≥48,连接页双栏/节点双列网格),
  复用同一批原子组件,不 fork 页面。
- 语义 token 零硬编码;文案全部走 l10n;套餐数字对齐 §7;无支付表单/emoji 国旗。
- CI 红线词扫描扩展到 client/lib;新增 Flutter analyze+test CI 任务。
- 测试:连接状态机/额度/节点单测、连接键三态与卡片组件测试、
  golden(连接键三态/推荐卡/额度卡 × 明暗)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:32:36 +08:00