wangjia
e9dbe9aa7d
chore: bump 1.0.31+32
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 23:21:18 +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
cd143af248
chore: bump 1.0.30+31
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 21:28:08 +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
23bf3077c8
docs(kb): 流量记账口径知识库 — 连接页实时(含直连) vs 统计页累计(仅代理)
...
记录两处上下行数字口径差异:连接页来自内核 Clash API 全局吞吐含直连,
统计页来自节点 v2ray_api per-user 计数仅代理;差额=直连,设计使然。
含对账口径警示。登记进 docs/index.html 知识库分类。决策:保持现状不改连接页口径。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 21:04:50 +08:00
wangjia
2b20a611d7
docs(test): 记录本轮交互组件人工验证(确认框/搜索框/toast);toast 超长文本待测
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 20:33:48 +08:00
wangjia
701d9eb095
fix(client): 开机自启隐藏窗口在原生 runner 层跳过首帧 Show()(1.0.29)
...
1.0.28 的 Dart hide() 早于 Flutter Windows runner 首帧回调的 Show(),被覆盖,
窗口仍弹出。改在 flutter_window.cpp 检测命令行 --autostart 时不注册首帧 Show()
回调,窗口(WS_OVERLAPPEDWINDOW 无 WS_VISIBLE)保持隐藏;Dart hide() 保留以同步
window_manager 状态。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 20:20:55 +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
143104b861
docs(design): toast 规格入真相源 + 铁律14「UI 组件必须取自真相源」
...
- design/CLAUDE.md §1 加铁律 14:新增/改 UI 组件必须用真相源已定义的组件与令牌;
真相源没有的先补规格再用,绝不在业务代码凭空造样式(如 Material 默认外观);须符合当前设计。
- design/CONTRACT.md §2 加 Toast/SnackBar 组件规格(surface 底/fg1 字/border 描边/md 圆角/floating,
明暗自动适配),canonical 实现=pangolin_theme snackBarTheme。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:43:54 +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
wangjia
28f8e5ebcd
fix(client): 自动连接竞态真因 — 等设置加载完再判(1.0.22)
...
日志实证:节点就绪(~450ms)与设置加载几乎同时完成,节点回调早 1ms 读到还没加载完的
autoConnect=false → 不连。AppSettings 加 loaded 标志(_load 完成置 true,失败也置);
_maybeAutoConnect 同时监听 nodes + settings,二者都就绪(settings.loaded && 节点 uuid 非空)
才决策一次。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:08:49 +08:00
wangjia
ff2f2f3c07
fix(client): 自动连接回调改用强类型 ref.read + 日志落文件(1.0.21)
...
- 自动连接回调不再用 listenManual 的 next(裸订阅会退化成 dynamic → valueOrNull 扩展崩),
改 ref.read(nodesProvider) 拿强类型 AsyncValue,彻底躲开 dynamic 扩展坑。
- logLine 除 stdout 外落桌面端固定文件(%LOCALAPPDATA%\Pangolin\pangolin.log,每次启动截断),
GUI/管理员启动也能拿日志,免 Tee。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 19:01:48 +08:00
wangjia
7fdadace4e
fix(client): 自动连接回调 NoSuchMethod(valueOrNull on dynamic)— 真正根因
...
日志显示每次节点加载,_maybeAutoConnect 回调抛 NoSuchMethodError:AsyncData 没有 valueOrNull
(在判 autoConnect 之前就崩)→ 自动连接从未生效。根因:sub 声明成裸 ProviderSubscription
(=<dynamic>),逼 listenManual 推断成 dynamic,next 变 dynamic;valueOrNull 是扩展 getter,
dynamic 上不解析 → 运行期 NoSuchMethod。修:sub 标全泛型 ProviderSubscription<AsyncValue<List<Node>>>。
(1.0.18 的竞态修复是误判,真凶是这个崩溃。)诊断日志暂留,确认后清。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 18:46:57 +08:00
wangjia
f734126eb6
chore(client): 1.0.19 — 自动连接诊断日志
...
排查「自动连接没生效」:在 _RootFlow 自动连接路径(HomeShell 显示 / 节点就绪 / autoConnect/phase 判定 / 触发)
与 Settings(_load 读到的 autoConnect、setAutoConnect 落盘)加 logLine 日志,定位卡在哪一步。诊断完移除。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 18:26:47 +08:00
wangjia
6beed211b9
fix(client): 自动连接竞态 + 版本 1.0.18+19
...
_maybeAutoConnect 启动瞬间同步读 settingsProvider.autoConnect,但其 _load 异步(SharedPreferences)
还没完成 → 读到默认 false 而永久跳过自动连接。改到「节点就绪」回调里再判 autoConnect(节点走网络
拉取,远慢于本地设置读取,届时设置必已加载到位)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 18:20:13 +08:00
wangjia
31b171d26c
fix(installer): 装完启动以管理员运行(runascurrentuser)
...
安装包 PrivilegesRequired=admin 提权运行,但默认 postinstall「启动程序」会降回普通
用户身份拉起 app(Inno RunAsOriginalUser)→ requireAdministrator 的 app 没管理员权、
TUN 建不了。[Run] 加 runascurrentuser,用安装包的管理员令牌拉起,装完即管理员运行。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 18:15:44 +08:00
wangjia
9d18579c11
chore(client): 版本号 1.0.17+18(智能选择确认/搜索框配色/PangolinFieldBox/连通看门狗/自动连接+Win自启)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 17:54:33 +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
0820397a14
chore(client): 版本号 1.0.16+17(single-flight 刷新 + 托盘退出崩溃修复)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:46:16 +08:00
wangjia
4cc8f772e5
fix(client): 托盘「退出」崩溃(0xC000041D)——绕开 windowManager.destroy()
...
Windows 上从托盘菜单退出会崩:windowManager.destroy() 在 Win32 托盘回调里触发
DestroyWindow/WM_CLOSE 回调链(并回调 onWindowClose → await isPreventClose),
异常穿过内核回调边界 → STATUS_FATAL_USER_CALLBACK_EXCEPTION(0xC000041D),
flutter_windows.dll 处崩溃。WER 崩溃签名跨版本一致(自 1.0.0 起),非业务代码引入。
内核已在 onBeforeQuit 停掉、隧道已拆,改为先 trayManager.destroy() 摘图标,
再 exit(0) 干净退出进程,绕开原生窗口销毁回调链。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:30:59 +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
eafc7167e2
chore(client): 版本号 1.0.15+16(自适应菜单 + 节点切换防误触)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 10:11:48 +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
878cef9e52
build(client): 1.0.12 — 设备下拉接已登录设备 + 版本对齐(pubspec=iss=1.0.12)
...
ci-pangolin / Lint — shellcheck (push) Successful in 5s
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 9s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m6s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 08:41:37 +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
ea548ad7cf
fix(deploy): agent 不再随 server 停 —— 解耦 systemd 依赖( #8 静默掉线根因)
...
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) Failing after 14s
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 10s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 13s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m3s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
Requires=pangolin-server → Wants=(软依赖);Restart=on-failure → always +
StartLimitIntervalSec=0。根因:Requires= 使每次重启 server 都把 agent 一起拖停
且不自起 → 节点静默 down。agent 本就靠 gRPC 自重连,不该绑 server 生死。
线上 /etc/systemd 已同步 daemon-reload,并重启 server 验证 agent 全程存活+重连。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 08:22:20 +08:00
wangjia
3a8e27b5b0
build(client/windows): 版本 bump 1.0.10 → 1.0.11(设备会话管理+统计时区+设备过滤)
...
ci-pangolin / Lint — shellcheck (push) Successful in 5s
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 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 16s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m8s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 08:08:47 +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
5dcebb77fd
feat(stats): 统计页设备下拉接真实设备( #10 第①层)
...
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 4s
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 4m6s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
_DeviceDropdown 改 ConsumerWidget:菜单 = 「全部」+ 近 30 天有用量的真实设备名
(来自 deviceUsageProvider);选中态存 statsDeviceProvider。无每设备用量时只显示
「全部」(稀疏,正常)。第②层「按设备过滤月/周/日卡+折线」待 per-device 每日曲线
API(#10 第②层),本提交只接下拉内容。tablet 统计 golden 无变化(空设备时同旧观感)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 07:04:20 +08:00
wangjia
f2499a64e1
fix(usage): 统计按客户端本地日聚合 —— 修「29号却显示28号数据」时区 bug
...
ci-pangolin / Lint — shellcheck (push) Successful in 7s
ci-pangolin / OpenAPI Sync Check (push) Successful in 16s
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 4s
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 4m8s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
根因:服务端用量全按 UTC 预切天,UTC+8 用户在中国 0-8 点时 UTC 还是前一天,
「今天」就显示前一天数据。
改法(用户拍板:小时桶+查询带时区偏移):
- migration 000017 `usage_hourly`(UTC 纪元小时整数桶,稀疏存储,空小时不落行)。
- ReportUsage 同时累加 usage_hourly(按 windowEnd 的 UTC 小时);usage_daily 保留作配额/今日。
- `UsageCurve(days, offsetMin)`:读 UTC 小时桶,按客户端时区偏移**重新聚合成本地日**曲线。
- `/v1/usage?tz_offset=分钟`;客户端 `usage()` 带 `DateTime.now().timeZoneOffset.inMinutes`。
- 客户端早已「取曲线最后一点当今日」,故只改服务端聚合 + 带偏移即可。
测试:store 时区分桶证明(UTC 18:00 在 UTC vs UTC+8 落不同本地日,锁住 bug 修复)+
migration v17 + usage_hourly 表;集成测试改喂 hourly;全量 go/flutter test 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 07:02:04 +08:00
wangjia
606e5e9e54
docs(plan): P4 收口 —— 每设备归因链路已交付,展示层过滤归 #10
...
每设备流量归因基础设施由 P1-P3 完整交付(注册→dp_uuid→usage_device_daily,
store 测试已锁)。统计页设备下拉「真过滤」需 per-device 每日曲线 API(现 provider
只给窗口聚合,对不齐月/周/日卡),归入统计整改 #10;端到端需真连客户端人工验证。
不做 ConnectNode 兜底(登录已注册;connect 体无 name/platform)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 06:14:19 +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
bcc114088c
feat(devices): P3 强制退出 + 清除增强(per-device 凭证吊销)
...
ci-pangolin / Lint — shellcheck (push) Successful in 9s
ci-pangolin / OpenAPI Sync Check (push) Successful in 17s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 5s
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) Successful in 12s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 15s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m4s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
后端:新端点 POST /v1/me/devices/{uuid}/logout(ForceLogout:吊销该设备会话+
丢 Redis JTI,设备留列表)。DeleteDevice 增强:先吊销会话再删设备(FK ON DELETE
CASCADE 清理会话行)+ 按 dp_uuid 吊销数据面凭证。CredentialRevoker 接口改
per-device RevokeDevice(dpUUID),由 nodes.Service 实现(查 connect_credentials
持有节点→推 CommandTypeRevoke + 删凭证行),main 注入替 NoopRevoker;devices 注入
SessionPort/JTIRevoker。修 SQLite 跨连接死锁(会话吊销移到 delete tx 之前)。
migration 000016 sessions FK 加 ON DELETE CASCADE。
客户端:account_api.forceLogout + devicesProvider.forceLogout(UI 留 P6)。
测试:ForceLogout(吊销会话+JTI+设备保留+403/404)+ DeleteDevice(级联+按 dp_uuid
吊销);NoopRevoker 改 dp_uuid;全量 server/flutter 测试绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 06:01:41 +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
8370ee1eb7
chore: 删除误入库的一次性临时文件(tmphash / zz_tmpverify_test)
...
ci-pangolin / Lint — shellcheck (push) Successful in 5s
ci-pangolin / OpenAPI Sync Check (push) Successful in 17s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 5s
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 10s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m9s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 15s
P1 提交时 git add -A 误带入早期改密码调试残留(注释自标「临时,不提交」)。
2026-06-29 00:28:54 +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
889cff4556
docs(plan): 设备&会话管理 实现计划 P1-P6(md执行源 + html阅读版 + 登记 + 链接)
...
新增 docs/superpowers/plans/2026-06-29-device-session-management.md(执行真相,
checkbox) + docs/device-session-management-plan.html(阅读版);登记进 index.html
「实现计划」;总方案顶部加「→ 实现计划」链接。P1 设备注册打通→P2 sessions表+
在线→P3 强制退出/清除→P4 每设备流量→P5 2FA(future)→P6 UI重做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 00:15:35 +08:00
wangjia
7fd2fed441
docs(design): §7 UI 改真实 token 视觉稿(替换字符画)
...
用 design/colors_and_type.css 真实色值(clay/sand 暖色浅色主题)做 HTML/CSS
还原稿,替换粗糙 ASCII:A 设备列表(在线绿点光晕/离线灰点+最后登录/本机标)
+ B 行尾 ⋯ 操作菜单(强制退出/清除登录信息危险红) + C 清除危险二次确认弹窗。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 00:05:15 +08:00
wangjia
e8261e8984
docs(design): 换用户uuid不变(唯一键改 UNIQUE(user_id,uuid)) + 按设备去重健壮性
...
§3.2.1 ⑤:device_id 安装级、与账户无关→换用户不变;故 devices 唯一键须从
全局 UNIQUE(uuid) 改为 UNIQUE(user_id,uuid)(000016),同机两账号=两行各自
dp_uuid。⑥:别用终身严格去重,改按活跃/在线计数+N天自动回收+用户自助清理。
§3.2 schema/表同步标注唯一键变更。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 23:54:52 +08:00