chore: 整理——gitignore android/.gradle 构建缓存 + 同步 todo 看板

- client/.gitignore 加 /android/.gradle/(gradle 构建缓存,不该入 git)。
- 同步 todo.json/html 到当前状态。

注:server/internal/pb/agent/v1 是 stray 生成产物(未被任何代码 import,代码用手写的
agentv1),未跟踪、不影响合并;清理需 rm,留给用户(! rm -rf server/internal/pb/agent)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 22:52:32 +08:00
parent 00bec95255
commit 53fb6492b1
3 changed files with 300 additions and 99 deletions
+1
View File
@@ -44,6 +44,7 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
/android/.gradle/
# iOS generated
ios/Flutter/Generated.xcconfig
+219 -85
View File
@@ -77,6 +77,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
.todo-card.s-accepted { border-left-color: #22c55e; background: #f0fdf4; opacity: .8; }
.card-header { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.item-id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 13px; color: #2563AC; background: #eef4fb; border: 1px solid #d3e2f3; border-radius: 6px; padding: 1px 7px; flex: 0 0 auto; }
.item-title { font-weight: 600; font-size: 15px; flex: 1; }
.todo-card.s-accepted .item-title { text-decoration: line-through; color: #52606d; }
.card-badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
@@ -252,13 +253,13 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<header>
<div class="wrap">
<h1>feature+windows — 项目 TODO</h1>
<div class="header-meta">生成于 2026-06-29 · 真相源 todo/todo.json</div>
<div class="header-meta">生成于 2026-06-30 · 真相源 todo/todo.json</div>
<div class="stats">
<div class="stat-pill"><strong>12</strong>全部</div>
<div class="stat-pill"><strong>7</strong>待开始</div>
<div class="stat-pill"><strong>16</strong>全部</div>
<div class="stat-pill"><strong>8</strong>待开始</div>
<div class="stat-pill"><strong>0</strong>开发中</div>
<div class="stat-pill"><strong>3</strong>待验收</div>
<div class="stat-pill"><strong>2</strong>已验收</div>
<div class="stat-pill"><strong>0</strong>待验收</div>
<div class="stat-pill"><strong>8</strong>已验收</div>
</div>
</div>
@@ -292,7 +293,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="filter-sep"></div>
<div class="filter-group">
<span class="filter-label">平台 / 标签</span>
<button class="filter-chip" data-filter-tag="Android">Android</button><button class="filter-chip" data-filter-tag="Windows">Windows</button><button class="filter-chip" data-filter-tag="mac">mac</button><button class="filter-chip" data-filter-tag="前端">前端</button><button class="filter-chip" data-filter-tag="后端">后端</button><button class="filter-chip" data-filter-tag="运维">运维</button>
<button class="filter-chip" data-filter-tag="Android">Android</button><button class="filter-chip" data-filter-tag="Windows">Windows</button><button class="filter-chip" data-filter-tag="gRPC">gRPC</button><button class="filter-chip" data-filter-tag="iOS">iOS</button><button class="filter-chip" data-filter-tag="mac">mac</button><button class="filter-chip" data-filter-tag="前端">前端</button><button class="filter-chip" data-filter-tag="后端">后端</button><button class="filter-chip" data-filter-tag="运维">运维</button>
</div>
</div>
@@ -301,7 +302,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="section-block" id="section-open">
<div class="section-title st-open" data-toggle="open">
📋 待开始 <span class="s-count">7</span>
📋 待开始 <span class="s-count">8</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-open">
@@ -314,6 +315,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="2"
data-tags="mac">
<div class="card-header">
<span class="item-id">#1</span>
<span class="item-title">macOS 原生 KillSwitch 补齐 L0→L3</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -343,6 +345,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="2"
data-tags="Android">
<div class="card-header">
<span class="item-id">#2</span>
<span class="item-title">Android KillSwitch L1 + 引导系统 Always-on</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -372,6 +375,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="3"
data-tags="后端,运维">
<div class="card-header">
<span class="item-id">#4</span>
<span class="item-title">线上启用 TOTP 两步验证并验 2FA</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -401,6 +405,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="2"
data-tags="后端">
<div class="card-header">
<span class="item-id">#8</span>
<span class="item-title">agent 掉线告警 + 静默吞错全量审计</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -430,6 +435,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="1"
data-tags="前端,后端">
<div class="card-header">
<span class="item-id">#12</span>
<span class="item-title">协议选择(REALITY/Hysteria2/自动)端到端</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -452,6 +458,66 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
</li>
<li class="todo-card s-open"
data-id="14"
data-level="mid"
data-status="open"
data-tier="1"
data-tags="Android,iOS,后端">
<div class="card-header">
<span class="item-id">#14</span>
<span class="item-title">移动端真·常连:Android Always-on + iOS/iPad On-Demand(无UI自连)</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-high">重要</span>
<span class="tag tier-1">一级</span>
</div>
</div>
<div class="item-desc">系统在 app 未开时拉起隧道:Android Always-on VPN(系统设置开启+VpnService 支持 onStartCommand 无UI拉起+配置缓存)、iOS/iPad On-Demand(NEOnDemandRule + PacketTunnelProvider 用缓存 lastConfigJson 自连)。难点:paid 凭证 TTL 仅24h,长期常连需原生侧刷新配置/凭证。桌面自启+全平台自动连接已在另一轮做(plan crystalline-exploring-lerdorf)。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="Android">Android</span> <span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-29</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="16"
data-level="mid"
data-status="open"
data-tier="1"
data-tags="后端,前端">
<div class="card-header">
<span class="item-id">#16</span>
<span class="item-title">启用设备数量限制 + 超限 UX(踢旧/选择移除,非硬拒登)</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-high">重要</span>
<span class="tag tier-1">一级</span>
</div>
</div>
<div class="item-desc">现状:plans.max_devices(free1/pro5/team10)+ devices/service.go:216 强制逻辑 + DEVICE_LIMIT_EXCEEDED 错误码 + 账户API DevicesMax 都在,但 main.go:534 登录注册传 MaxDevices:0(不启用)。原因:free 用户重装会换 device UUID,登录硬卡会把人锁外面。需:① 登录/注册从套餐读 max_devices 传入 RegisterIfAbsent;② 超限不直接拒登,改为'踢最旧设备/让用户选哪台下线'的 UX(客户端弹窗 + 设备管理页操作);③ 处理 free 重装 churn(同机复用而非每次新增)。tier-1:涉及登录接口 + 跨后端/前端 + 策略设计。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="前端">前端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-30</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="3"
data-level="low"
@@ -459,6 +525,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="1"
data-tags="Windows">
<div class="card-header">
<span class="item-id">#3</span>
<span class="item-title">Windows KillSwitch L1→L2 (WFP 防火墙强制)</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
@@ -477,35 +544,6 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="item-meta">
<span class="meta-date">🕐 2026-06-22</span>
</div>
</div>
</li>
<li class="todo-card s-open"
data-id="11"
data-level="low"
data-status="open"
data-tier="3"
data-tags="前端">
<div class="card-header">
<span class="item-title">清理旧命名 tablet golden 孤儿文件</span>
<div class="card-badges">
<span class="tag status-badge s-open">待开始</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
</div>
</div>
<div class="item-desc">client/test/golden/goldens/ 下未被引用的 tablet_connect.png/tablet_connect_dark.png/tablet_servers.png/tablet_stats.png/tablet_account.png(旧命名,已被 _light_/_dark_zh 矩阵取代),git rm 清掉。(从本地 main todo 合并而来)</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
</div>
</div>
</li>
@@ -525,26 +563,68 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
</div>
<div class="section-block" id="section-done">
<div class="section-title st-done" data-toggle="done">
🔍 待验收 <span class="s-count">3</span>
🔍 待验收 <span class="s-count">0</span>
<span class="s-arrow">▴ 收起</span>
</div>
<div class="section-list-wrap " id="list-wrap-done">
<ul class="todo-list" id="list-done">
<p class="empty-tip">暂无条目</p>
</ul>
</div>
</div>
<div class="section-block" id="section-accepted">
<div class="section-title st-accepted" data-toggle="accepted">
✅ 已验收 <span class="s-count">8</span>
<span class="s-arrow">▾ 展开</span>
</div>
<div class="section-list-wrap collapsed" id="list-wrap-accepted">
<ul class="todo-list" id="list-accepted">
<li class="todo-card s-done"
<li class="todo-card s-accepted"
data-id="11"
data-level="low"
data-status="accepted"
data-tier="3"
data-tags="前端">
<div class="card-header">
<span class="item-id">#11</span>
<span class="item-title">清理旧命名 tablet golden 孤儿文件</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-3">三级</span>
</div>
</div>
<div class="item-desc">client/test/golden/goldens/ 下未被引用的 tablet_connect.png/tablet_connect_dark.png/tablet_servers.png/tablet_stats.png/tablet_account.png(旧命名,已被 _light_/_dark_zh 矩阵取代),git rm 清掉。(从本地 main todo 合并而来)</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
</div>
</li>
<li class="todo-card s-accepted"
data-id="7"
data-level="high"
data-status="done"
data-status="accepted"
data-tier="1"
data-tags="后端">
<div class="card-header">
<span class="item-id">#7</span>
<span class="item-title">节点健康绑定 agent 在线 + connect 校验凭证下发 + 掉线告警</span>
<div class="card-badges">
<span class="tag status-badge s-done">验收</span>
<span class="tag status-badge s-accepted">验收</span>
<span class="tag t-block">高优 · 紧急</span>
<span class="tag tier-1">一级</span>
<button class="reject-btn" data-id="7" data-title="节点健康绑定 agent 在线 + connect 校验凭证下发 + 掉线告警">拒绝验收</button>
</div>
</div>
@@ -555,54 +635,26 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
</div>
</li>
<li class="todo-card s-done"
data-id="9"
data-level="mid"
data-status="done"
data-tier="2"
data-tags="前端,后端">
<div class="card-header">
<span class="item-title">打通每设备归因(设备注册 + per-device dp_uuid)</span>
<div class="card-badges">
<span class="tag status-badge s-done">待验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-2">二级</span>
<button class="reject-btn" data-id="9" data-title="打通每设备归因(设备注册 + per-device dp_uuid)">拒绝验收</button>
</div>
</div>
<div class="item-desc">现状:多设备共用账户级 dp_uuid,统计无法区分设备;usage_device_daily 永远空,统计页设备下拉只有「全部」。基础设施已具备(devices.dp_uuid / EnsureDeviceDpUUID / usage_device_daily / UserDeviceByDpUUID)但未激活——客户端没 POST /v1/me/devices 注册本机,连接时 EnsureDeviceDpUUID 查不到设备行→回退账户 uuid。需:① 客户端启动/登录时注册本机设备(稳定 uuid + name + platform);② 连接时带该 device_id;③ 或让 EnsureDeviceDpUUID 缺行时自动建设备行。打通后每设备统计 + 统计页设备下拉才有真数据。是统计重设计「设备下拉」的前置依赖。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
</div>
</div>
</li>
<li class="todo-card s-done"
<li class="todo-card s-accepted"
data-id="10"
data-level="low"
data-status="done"
data-status="accepted"
data-tier="2"
data-tags="前端">
<div class="card-header">
<span class="item-id">#10</span>
<span class="item-title">统计页设备下拉接真实设备数据(依赖#上条)</span>
<div class="card-badges">
<span class="tag status-badge s-done">验收</span>
<span class="tag status-badge s-accepted">验收</span>
<span class="tag t-low">一般 / 优化</span>
<span class="tag tier-2">二级</span>
<button class="reject-btn" data-id="10" data-title="统计页设备下拉接真实设备数据(依赖#上条)">拒绝验收</button>
</div>
</div>
@@ -613,21 +665,101 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
</div>
</li>
</ul>
<li class="todo-card s-accepted"
data-id="15"
data-level="mid"
data-status="accepted"
data-tier="2"
data-tags="iOS,前端">
<div class="card-header">
<span class="item-id">#15</span>
<span class="item-title">iOS/iPad 实时统计+urltest 延迟与 macOS 对齐</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-2">二级</span>
</div>
</div>
<div class="item-desc">移植 macOS 已验证的统计架构:扩展注入 clash_api + StatsCollector(clash 取 urltest),app StatsClient 改 sendProviderMessage 轮询;前置生成 iOS Libbox.xcframework。Dart 侧已对齐(共用 VpnNativeBridge)。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="iOS">iOS</span> <span class="tag t-tag" data-tag="前端">前端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-30</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
</div>
<div class="section-block" id="section-accepted">
<div class="section-title st-accepted" data-toggle="accepted">
✅ 已验收 <span class="s-count">2</span>
<span class="s-arrow">▾ 展开</span>
</div>
</li>
<li class="todo-card s-accepted"
data-id="9"
data-level="mid"
data-status="accepted"
data-tier="2"
data-tags="前端,后端">
<div class="card-header">
<span class="item-id">#9</span>
<span class="item-title">打通每设备归因(设备注册 + per-device dp_uuid)</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
<span class="tag t-high">重要</span>
<span class="tag tier-2">二级</span>
</div>
</div>
<div class="item-desc">现状:多设备共用账户级 dp_uuid,统计无法区分设备;usage_device_daily 永远空,统计页设备下拉只有「全部」。基础设施已具备(devices.dp_uuid / EnsureDeviceDpUUID / usage_device_daily / UserDeviceByDpUUID)但未激活——客户端没 POST /v1/me/devices 注册本机,连接时 EnsureDeviceDpUUID 查不到设备行→回退账户 uuid。需:① 客户端启动/登录时注册本机设备(稳定 uuid + name + platform);② 连接时带该 device_id;③ 或让 EnsureDeviceDpUUID 缺行时自动建设备行。打通后每设备统计 + 统计页设备下拉才有真数据。是统计重设计「设备下拉」的前置依赖。</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="后端">后端</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-28</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
<div class="section-list-wrap collapsed" id="list-wrap-accepted">
<ul class="todo-list" id="list-accepted">
</div>
</li>
<li class="todo-card s-accepted"
data-id="13"
data-level="high"
data-status="accepted"
data-tier="1"
data-tags="后端,前端,gRPC">
<div class="card-header">
<span class="item-id">#13</span>
<span class="item-title">数据面故障韧性:节点健康上报+客户端连通看门狗</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
<span class="tag t-block">高优 · 紧急</span>
<span class="tag tier-1">一级</span>
</div>
</div>
<div class="item-desc">agent 心跳带 sing-box 健康(clash_api /version)→控制面 effectiveNodeStatus 标 down(列表置灰+connect拦截);客户端连上后 HTTP 204 看门狗,连续失败:智能选择自动切节点/手动只提示断开。plan: crystalline-exploring-lerdorf.md</div>
<div class="card-footer">
<div class="tag-row"><span class="tag t-tag" data-tag="后端">后端</span> <span class="tag t-tag" data-tag="前端">前端</span> <span class="tag t-tag" data-tag="gRPC">gRPC</span></div>
<div class="item-meta">
<span class="meta-date">🕐 2026-06-29</span>
<span class="meta-date">✅ 验收 2026-06-30</span>
</div>
</div>
</li>
<li class="todo-card s-accepted"
data-id="6"
data-level="mid"
@@ -635,6 +767,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="2"
data-tags="前端,后端">
<div class="card-header">
<span class="item-id">#6</span>
<span class="item-title">节点列表:正式名(地区·国家) + 国旗图标</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
@@ -664,6 +797,7 @@ ul.todo-list { list-style: none; margin: 0; padding: 0; }
data-tier="3"
data-tags="后端">
<div class="card-header">
<span class="item-id">#5</span>
<span class="item-title">SendCode 发信失败记日志 + 测试</span>
<div class="card-badges">
<span class="tag status-badge s-accepted">已验收</span>
+80 -14
View File
@@ -1,9 +1,9 @@
{
"meta": {
"title": "feature+windows — 项目 TODO",
"updated_at": "2026-06-29T00:04:33.913Z"
"updated_at": "2026-06-30T14:47:39.344Z"
},
"seq": 12,
"seq": 17,
"items": [
{
"id": 1,
@@ -106,10 +106,10 @@
"tags": [
"后端"
],
"status": "done",
"status": "accepted",
"created_at": "2026-06-28T00:26:44.498Z",
"done": false,
"completed_at": null,
"done": true,
"completed_at": "2026-06-30T14:44:35.082Z",
"version": null
},
{
@@ -137,10 +137,10 @@
"前端",
"后端"
],
"status": "done",
"status": "accepted",
"created_at": "2026-06-28T09:32:26.008Z",
"done": false,
"completed_at": null,
"done": true,
"completed_at": "2026-06-30T14:44:34.810Z",
"version": null
},
{
@@ -152,10 +152,10 @@
"tags": [
"前端"
],
"status": "done",
"status": "accepted",
"created_at": "2026-06-28T09:32:26.080Z",
"done": false,
"completed_at": null,
"done": true,
"completed_at": "2026-06-30T14:44:34.991Z",
"version": null
},
{
@@ -167,10 +167,10 @@
"tags": [
"前端"
],
"status": "open",
"status": "accepted",
"created_at": "2026-06-28T11:27:56.637Z",
"done": false,
"completed_at": null,
"done": true,
"completed_at": "2026-06-30T14:47:39.343Z",
"version": null
},
{
@@ -188,6 +188,72 @@
"done": false,
"completed_at": null,
"version": null
},
{
"id": 13,
"title": "数据面故障韧性:节点健康上报+客户端连通看门狗",
"desc": "agent 心跳带 sing-box 健康(clash_api /version)→控制面 effectiveNodeStatus 标 down(列表置灰+connect拦截);客户端连上后 HTTP 204 看门狗,连续失败:智能选择自动切节点/手动只提示断开。plan: crystalline-exploring-lerdorf.md",
"level": "high",
"tier": 1,
"tags": [
"后端",
"前端",
"gRPC"
],
"status": "accepted",
"created_at": "2026-06-29T04:55:46.733Z",
"done": true,
"completed_at": "2026-06-29T16:59:39.926Z",
"version": null
},
{
"id": 14,
"title": "移动端真·常连:Android Always-on + iOS/iPad On-Demand(无UI自连)",
"desc": "系统在 app 未开时拉起隧道:Android Always-on VPN(系统设置开启+VpnService 支持 onStartCommand 无UI拉起+配置缓存)、iOS/iPad On-Demand(NEOnDemandRule + PacketTunnelProvider 用缓存 lastConfigJson 自连)。难点:paid 凭证 TTL 仅24h,长期常连需原生侧刷新配置/凭证。桌面自启+全平台自动连接已在另一轮做(plan crystalline-exploring-lerdorf)。",
"level": "mid",
"tier": 1,
"tags": [
"Android",
"iOS",
"后端"
],
"status": "open",
"created_at": "2026-06-29T09:36:14.004Z",
"done": false,
"completed_at": null,
"version": null
},
{
"id": 15,
"title": "iOS/iPad 实时统计+urltest 延迟与 macOS 对齐",
"desc": "移植 macOS 已验证的统计架构:扩展注入 clash_api + StatsCollector(clash 取 urltest),app StatsClient 改 sendProviderMessage 轮询;前置生成 iOS Libbox.xcframework。Dart 侧已对齐(共用 VpnNativeBridge)。",
"level": "mid",
"tier": 2,
"tags": [
"iOS",
"前端"
],
"status": "accepted",
"created_at": "2026-06-30T10:56:48.894Z",
"done": true,
"completed_at": "2026-06-30T14:44:34.900Z",
"version": null
},
{
"id": 16,
"title": "启用设备数量限制 + 超限 UX(踢旧/选择移除,非硬拒登)",
"desc": "现状:plans.max_devices(free1/pro5/team10)+ devices/service.go:216 强制逻辑 + DEVICE_LIMIT_EXCEEDED 错误码 + 账户API DevicesMax 都在,但 main.go:534 登录注册传 MaxDevices:0(不启用)。原因:free 用户重装会换 device UUID,登录硬卡会把人锁外面。需:① 登录/注册从套餐读 max_devices 传入 RegisterIfAbsent;② 超限不直接拒登,改为'踢最旧设备/让用户选哪台下线'的 UX(客户端弹窗 + 设备管理页操作);③ 处理 free 重装 churn(同机复用而非每次新增)。tier-1:涉及登录接口 + 跨后端/前端 + 策略设计。",
"level": "mid",
"tier": 1,
"tags": [
"后端",
"前端"
],
"status": "open",
"created_at": "2026-06-30T14:33:48.996Z",
"done": false,
"completed_at": null,
"version": null
}
]
}