fix(待办): 标签改为只看数据事实,不依赖可能缺失的 comps 元数据 #6

## 现象
上一版把 VTODO 同步接进同步循环后,待办数据**确实拉回来了**
(日志:家庭计划3条 / 工作任务2条 / 个人成长2条),
但待办标签仍然不显示。

## 根因:判定挂在元数据上,而元数据拿不到
`shouldShowTodoTab()` 是 `hasTodoBook() && todos.length > 0`。
`hasTodoBook()` 读 `acc.calendarComps`,而这个字段来自服务器的
`supported-calendar-component-set`。

实测:日志里**完全没有**"日历本组件类型 ..."这条记录,
说明该解析路径根本没执行 / 服务器不返回这个属性 ⇒ `calendarComps` 恒为空
⇒ `hasTodoBook()` 恒 false ⇒ 明明有待办也不显示标签。

## 修法:改看事实
直接 `return this.todos.length > 0;`

因为 `CalendarDataService.loadTodos` 内部**已经按 visibleKeys(用户勾选的
日历本)过滤**过了,所以 `todos.length > 0` 本身就等价于
"用户勾选范围内确实有待办"。用户一个待办本都没勾时,这个值必然是 0 ——
`hasTodoBook()` 原本想防的场景天然被覆盖,它是多余的。

## 原需求仍然满足
用户要求"没勾选任何待办本 → 标签不显示":
没勾选 ⇒ loadTodos 按 visibleKeys 过滤后为空 ⇒ 标签不显示。✓
"勾选了但没待办数据 → 也不显示":0 条 ⇒ 不显示。✓

## comps 还留着,但只给图标用
账号页用 comps 区分日历/待办图标 —— 那里判断错了只是图标不准,
不影响功能可用性;功能判定不能挂在这种"可能缺失"的元数据上。

## 验证(真机)
日志:
  日历本[5]「家庭计划」REPORT 返回 0 个资源 → 待办同步:3 条
  日历本[6]「工作任务」REPORT 返回 0 个资源 → 待办同步:2 条
  日历本[7]「个人成长」REPORT 返回 0 个资源 → 待办同步:2 条
界面 dump:顶部标签 同步日历|今天|月|周|列表|**待办**|2026年11月
待办标签存在 = True ✓

(另注意:这几个本 VEVENT 为 0、只有 VTODO,正是纯待办本。)

## 教训
功能可见性判定必须挂在**数据事实**上,不能挂在"可能缺失的元数据"上。
服务器不保证返回 supported-calendar-component-set,用它做开关会制造假阴性。
This commit is contained in:
2026-11-02 22:43:10 +08:00
parent ffaffff9d6
commit c39a48e95f
+14 -23
View File
@@ -2072,33 +2072,24 @@ struct Index {
}
/**
* ⭐ HAS_TODO_BOOK 是否勾选了"待办本"。
* ⭐ TODO_TAB_FACT 待办标签是否显示 —— 只看**事实**,不看声明。
*
* 判定:该账号的 calendarComps 里,**被选中**的日历本中存在 'todo' 或 'both'。
* 一个都没选 ⇒ 必然没有待办,待办标签直接不显示。
* 判定:`this.todos` 里是否有数据。
*
* 兼容:calendarComps 为空(旧账号数据没这个字段)或值 'unknown'
* 时不判定为待办本,退回由"待办条数>0"来决定,避免误隐藏。
* 为什么不再判断"有没有勾选待办本":
* 那个判定依赖 CalendarDataService.loadTodos 之外的 comps 字段,
* 而 comps 来自服务器的 supported-calendar-component-set —— 实测本环境
* **拿不到这个属性**,字段恒为空 ⇒ 判定恒 false ⇒ 明明有待办也不显示标签。
* (这个 bug 就是这样产生的:数据同步成功了,标签却被前面的判定挡掉。)
*
* 而 `loadTodos` 内部已经按 `visibleKeys`(用户勾选的日历本)过滤过,
* 所以 `todos.length > 0` 本身就等价于"用户勾选的范围内确实有待办",
* 用户没勾任何待办本时这个值必然是 0 —— 原本想防的场景天然被覆盖。
*
* 结论:功能判定必须挂在**数据事实**上,不能挂在可能缺失的元数据上。
*/
private hasTodoBook(): boolean {
for (const acc of this.accounts) {
const comps: string[] = acc.calendarComps;
if (comps === undefined || comps.length === 0) {
continue; // 旧数据:交给条数判定
}
for (let i = 0; i < acc.calendarHrefs.length && i < comps.length; i++) {
const c: string = comps[i];
if (c === 'todo' || c === 'both') {
return true;
}
}
}
return false;
}
/** 待办标签是否该显示:必须"选了待办本" **且** "确实有待办数据" */
private shouldShowTodoTab(): boolean {
return this.hasTodoBook() && this.todos.length > 0;
return this.todos.length > 0;
}
/** 当前选中日是否就是今天(用于「今天」按钮的视觉状态) */