Commit Graph

13 Commits

Author SHA1 Message Date
wangjia 9f2f96d722 merge: maestro/tsk_xAQhC1xuCd8x into main (bulk integration) 2026-06-17 00:41:30 +08:00
wangjia fe16f89bc3 feat(client): 账户子页 desktop 内容区下钻(档1)
- desktop 上套餐选择/兑换购买/设备管理改为内容区下钻(侧栏保留、shell 顶栏带
  返回箭头),不再全屏 push 盖住侧栏;mobile/tablet 仍全屏 push
- _SubScaffold 加 embedded 参数(true 只返回内容),PlansScreen/DevicesScreen/
  RedeemScreen 透传;NavView 加 devices + kAccountSubViews 集合
- desktop_shell 渲染子页(embedded) + 顶栏 onBack→account;account_page 按
  formFactor 选下钻(切 navView)或 push
- desktop 整页 golden 补 套餐/兑换/设备 三子页(共 8 页)

测试: flutter test 84 通过(+3 子页 golden), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:36: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 22e4eb1ead feat(client): desktop 其他页精还原 + 整页 golden 闸
- stats_page 对照 dapp.jsx DStats 精对齐: padding 32/8、指标卡 padding 16/18·
  gap 14·值 mono 24、柱状图卡 padding 20/18·标题 13、柱 height 140(容 bar 86)·宽 34
- nodes_page 对照 DServers 微调: padding top 4、网格 gap 10(智能卡置顶保留, 符 §5)
- 新增 test/golden/desktop_pages_golden_test.dart: 用 macOS 平台覆盖 + 920×600 surface
  让 formFactor=desktop, 截 节点/统计/账户 三页整页 golden 作回归闸(连接页含旋转动画
  由组件级 golden 覆盖, 不在此截)

测试: flutter test 79 通过(+3 desktop 整页 golden), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 12:02:24 +08:00
wangjia 1340b477a7 feat(client): 字体可注入 + 启用 golden 视觉回归闸
- pangolin_theme: PangolinFonts 加 useBundled 开关 + sora/manrope/jetBrainsMono/
  manropeTextTheme 封装(生产走 google_fonts / 测试走 bundled family);
  PangolinText 与 _build 改调封装,移除散落的 GoogleFonts 直接调用
- test/flutter_test_config.dart: 全测试前置,FontLoader 注册 test/fonts 真实
  Sora/Manrope/JetBrainsMono + Lucide 图标字体,置 useBundled=true
- 启用 test/golden(去 @Skip):连接键三态+推荐卡+额度卡×明暗 12 基准图
- tool/visual-diff.md: 标注 golden 严格闸已落地

测试: flutter test 76 通过(原 64+1skip → 76 无 skip), analyze 0 error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 11:36:50 +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 83dd5369ab feat(M4/M5): URLTest 自动选线 + Kill-switch + 弹性重连 [tsk_xAQhC1xuCd8x]
M4 URLTest 自动选线:
- ClashApiClient.getGroupDelay() — GET /group/<name>/delay 触发按需测速
- ClashApiClient.extractUrltestResults() — 从 /proxies 响应解析 URLTest
  组成员最新延迟,填充 stats 帧的 urltestResults 字段
- DesktopKernelProcess._startStatsPoll() — 每秒并行拉取连接统计 + URLTest
  延迟(_clashApi.getProxies()),URLTest 失败不影响主统计
- DesktopVpnBridge.selectOutbound(tag) — Clash API PUT /proxies/proxy,
  tag 可传节点 tag 或 urltest 组 tag(如 "auto-select")恢复自动
- DesktopVpnBridge.getActiveOutbound() — GET /proxies 读 proxy.now
- app/kernel/poc/reality_client.config.json.tmpl — 增加 urltest("auto-select")
  + selector("proxy") 出口组,route.final 改为 "proxy"

M5 Kill-switch + 弹性重连:
- DesktopVpnBridge.setKillSwitch({required bool on}) — 将 _killSwitchEnabled
  写入 TUN inbound strict_route 字段(applyKillSwitchToConfig);内核运行中
  触发静默重载(kill → start(lastConfig))
- DesktopVpnBridge.applyKillSwitchToConfig() — static,killSwitch=true ↔
  TUN strict_route=true,killSwitch=false ↔ strict_route=false
- 自动退避重连:kernel 崩溃推 error → _scheduleReconnect() 退避序列
  1s/2s/4s/8s/16s/30s(上限),stop() 取消,重连成功后重置计数器
- DesktopKernelProcess 意外退出清理 _clashApi(避免下次 spawn 残留)
- app/pangolin/test/killswitch_checklist.md — 三端故障注入验收清单
  (Desktop ,iOS/Android 欠账说明)

新增测试(client/test/bridge/desktop_vpn_bridge_m4m5_test.dart):
- M4: extractUrltestResults 解析/空/无 history 边界
- M4: getGroupDelay HTTP 请求格式验证
- M4: selectOutbound / getActiveOutbound Clash API 调用断言
- M5: applyKillSwitchToConfig 字段覆盖 + 无 TUN 边界
- M5: setKillSwitch 状态注入 + 同值幂等
- M5: 崩溃→error 不崩 / 首次重连在退避延迟内触发 / stop 后不再重连

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:11:09 +08:00
wangjia 3c6a8a517a feat: 桌面端 PoC M1 — sing-box 子进程 TUN 接线 [tsk_SLCsjNgtmng3]
实现桌面端(macOS PoC)内核子进程管理,打通 sing-box TUN 模式全链路:

### client/lib/bridge/kernel_process.dart(完整实现,替换原有 stub)
- ClashApiClient: HTTP 客户端,支持 /connections / /proxies / /traffic(SSE) / /traffic(plain)
  - Bearer Token 鉴权;getConnections 用于就绪探测与流量统计;getTraffic SSE 读首帧后断开
- KernelProcess 接口: 新增 statusStream / statsStream 至接口定义
- DesktopKernelProcess:
  - spawn(configPath): 解析 Clash API 端口/secret → sudo sing-box run(macOS PoC)→
    轮询 /connections 等待就绪(20s 超时)→ emit connecting→on
  - kill(): SIGTERM + 等待 gracePeriod(5s) → SIGKILL → emit off
  - 意外退出: emit error(UI 可一键重连,不崩溃)
  - 统计轮询: 每秒 GET /connections,差分算 uploadSpeed/downloadSpeed
  - 二进制解析: ENV > exe同目录 > macOS Bundle Resources > 开发目录 > /usr/local/bin
- 辅助函数: generateClashApiPort(高位随机)、generateClashApiSecret(32B hex)

### client/lib/bridge/desktop_vpn_bridge.dart(新文件)
- DesktopVpnBridge implements VpnBridge:
  - start(configJson): injectClashApi(注入随机端口+secret)→ writeConfig(0600)→ kernel.spawn
  - stop(): kernel.kill(5s)
  - statusStream / statsStream: 代理 KernelProcess 事件流
  - selectOutbound: Clash API PUT /proxies/{group}
  - getActiveOutbound: 从 /proxies 读 now 字段
  - configDirOverride: 测试注入支持
  - injectClashApi: 静态方法,尊重已有 clash_api 配置,合并保留 experimental 其他字段

### client/test/bridge/kernel_process_test.dart(新文件)
- ClashApiClient 完整测试: headers / 解析 / PUT body / DELETE / 非 200 抛 HttpException
- generateClashApiPort / generateClashApiSecret 生成范围和格式测试
- DesktopVpnBridge.injectClashApi: 注入 / 尊重已有 / 保留字段 / 保留其他 experimental / 非法 JSON
- DesktopVpnBridge + FakeKernelProcess 集成: connecting→on / stop→off / statsStream / 意外退出 / spawn失败

### app/kernel/poc/(新目录)
- reality_client.config.json.tmpl: VLESS+REALITY+TUN 客户端配置模板
  - TUN inbound: auto_route + strict_route(macOS kill-switch 基础保护)
  - DNS: 防泄露(remote via VPN + cn 直连)
  - experimental.clash_api: 随机端口 + secret 占位符
- gen-poc-config.sh: 渲染模板为可用 JSON(从环境变量读 REALITY 参数)
- README.md: 完整 PoC 接线与 M1 验收步骤

提权说明(macOS PoC):
  · sudo 提权(开发机需配 /etc/sudoers.d/pangolin-singbox 或有 sudo 缓存)
  · 正式版: SMJobBless Helper + 公证(BACKLOG-11D-HELPER)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:53:50 +08:00
wangjia bf1717a369 merge: 控制面联调 M6 [tsk_nuoKSM4Vt-zK]
# Conflicts:
#	.gitignore
#	client/lib/widgets/home_shell.dart
#	client/lib/widgets/server_tile.dart
#	client/pubspec.yaml
2026-06-13 17:32:26 +08:00
wangjia 6f6cb97509 merge: Flutter 逐屏还原 + iPad 断点 [tsk_gpPXi-icyeOE]
# Conflicts:
#	client/lib/widgets/connect_button.dart
#	client/lib/widgets/home_shell.dart
#	todo/todo.html
#	todo/todo.json
2026-06-13 17:30:36 +08:00
wangjia c84b6ea7da merge: maestro/tsk_iP5-_Ztq5THx [桥接 API 面 Channel 契约 + 三端原生骨架] [tsk_rcNKc3GQHBUd]
合并冲突补救:原分支从 merge base 7871512 分叉,仅修改 client/ 目录,
与 main 新增的 server/(apierr/idgen/CONVENTIONS.md) 完全不重叠,
无真实文件冲突,手动应用 feature branch 的全部 client/ 改动。

client 侧新增:
- lib/bridge/vpn_bridge.dart      — Dart↔原生通道契约(冻结)
- lib/bridge/vpn_bridge_mock.dart — 假内核,UI 联调用
- lib/bridge/kernel_process.dart  — 桌面子进程管理接口
- ios/PacketTunnel/{Info.plist,PacketTunnelProvider.swift} — NEPacketTunnel 骨架
- ios/Runner/VpnManager.swift     — NETunnelProviderManager 封装
- android/.../PangolinVpnService.kt — VpnService 骨架
- android/.../VpnEventBus.kt      — Application 级状态总线
- test/bridge/vpn_bridge_mock_test.dart — 桥接层单元测试

client 侧修改:
- android/.../MainActivity.kt    — 注册三通道(MethodChannel + 2×EventChannel)
- android/.../AndroidManifest.xml — 声明 FOREGROUND_SERVICE + PangolinVpnService
- ios/Runner/AppDelegate.swift   — 注册通道 + VpnManager 初始化
- ios/Runner/Info.plist          — 新增 NSVPNUsageDescription
- ios/Runner.xcodeproj/project.pbxproj — 添加 PacketTunnel extension target
- lib/widgets/connect_button.dart — VpnStatus 迁移到 bridge/vpn_bridge.dart
- lib/widgets/home_shell.dart    — error 状态 UI 分支补全
2026-06-13 14:58:38 +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
wangjia bd8f974a25 feat(M6): 控制面联调 — mock connect server + 客户端 ConnectApi 透传
tsk_nuoKSM4Vt-zK

## 服务端(server/cmd/mockserver)
- `main.go`(79 行):独立 mock HTTP server,实现 POST /v1/nodes/:id/connect
  - 路径/请求体/响应体字段名与 §3.1 契约逐字一致
  - Bearer token 鉴权(-token 参数,空值跳过,不入库)
  - 缺少 device_id → 400;未授权 → 401;非 POST → 405
  - 返回完整 sing-box config JSON(tun + REALITY/Hy2 outbound + urltest + route/dns)
  - 注:mock 联调;待 #5/#6 真实 connect 接口就绪后替换
- `main_test.go`:6 项单测,覆盖 §3.1 结构校验、auth、method、path

## 客户端(client/)
- `lib/services/connect_api.dart`:ConnectApi 类
  - fetchConfig(nodeId, deviceId) → 原始响应体字符串(不做任何修改)
  - 错误路径:HTTP 非 200 / 超时 / 非法 JSON → ConnectApiException(含双语 message)
- `lib/services/vpn_bridge.dart`:VpnBridge stub(M6 联调,libbox 绑定待 11C)
  - start(configJson) 原样存储,不修改;stop() 清空
- `lib/widgets/home_shell.dart`:_toggle/_pick 替换为真实 API 流程
  - ConnectApi.fetchConfig → VpnBridge.start(透传,无中间变换)
  - 错误路径:ConnectApiException → SnackBar,status 回 off,无残留半开隧道
  - API URL/token/deviceId 由 --dart-define 注入(不入库)
- `lib/widgets/server_tile.dart`:ServerInfo 新增 nodeId 字段
- `pubspec.yaml`:新增 http: ^1.2.1 依赖
- `test/connect_passthrough_test.dart`:4 项透传断言单测
  - 核心:fetchConfig 返回字符串 === VpnBridge.start 接收字符串(逐字节相等)
  - 空格/格式原样保留(不经 jsonEncode 重序列化)
  - HTTP 错误、非法 JSON 错误路径覆盖

## 运行方式(M6 联调)
```
# 启动 mock server(无 auth)
go run ./server/cmd/mockserver -addr :8081

# 启动客户端(指向 mock server)
flutter run \
  --dart-define=PANGOLIN_API_URL=http://localhost:8081 \
  --dart-define=PANGOLIN_API_TOKEN=dev-mock-token \
  --dart-define=PANGOLIN_DEVICE_ID=demo-device-001
```

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:16:29 +08:00