Files
FinDataHub 54626a69fb refactor(client): 同步状态收敛为 SyncSession 值类型,修掉 reset 漏字段
## 问题

PythonProcessManager 上 20 个各自独立的 @Published 散落着,带来两个实际问题:

1. 重置靠逐个赋值。旧 reset() 实际漏了 6 个字段——syncRate / successCount /
   failCount / currentTableProgress / currentTableStocksDone /
   currentTableStocksTotal。症状是「第二轮同步一开始就显示上一轮的速率与成功率」,
   不报错、不崩溃,只能靠肉眼在界面上发现。
2. 每个字段各自触发一次 objectWillChange,一次状态推进可能连发数次刷新。

## 改动

新增 Models/SyncSession.swift(值类型,20 字段 + 默认值)。manager 上改为:

  @Published private(set) var session = SyncSession()
  private(set) var progress: Double { get { session.progress } set { … } }
  … 共 20 个同名转发属性

- View 层零改动:对外仍是同名属性,90+ 处读取照旧
- reset() 改为一条 `session = SyncSession()`,新增字段自动被重置
- setter 收为 private(set):外部只能读,写入口全收在类内(拆文件的必要前提)
- 未纳入 session 的两个字段:state(带 didSet 副作用,语义是状态机)、
  consecutiveFailureWarning(UI 横幅状态)

## 必须记住的坑

编译报 `failed to produce diagnostic for expression`,位置在 DashboardView /
SyncProgressView 的**最后一行**。真正原因是 #Preview 固件里在直接
`pm.progress = 0.45`,而 setter 已收为 private(set)——Swift 对大 body 的类型检查
失败会把错误位置退化到文件末行,看不出真正的出错点。

解法不是放开 setter,而是给预览/测试一个显式缝 replaceSession(_:),
注释写明「业务代码不要用:它绕过节流刷新、日志缓冲与状态机既有路径」。
另外 reset() 从 private 放宽到 internal,仅为了让回归测试能直接调用。

## 证伪

把 reset() 改回逐字段赋值(漏 6 个字段)后,testResetClearsAllSessionFields
精确报出 6 条失败,形如:

  XCTAssertEqual failed: ("3999") is not equal to ("0") - successCount 未被重置

恢复后 47 tests 全绿,grep 证伪实验 无残留。

## 回归

SyncSessionTests 5 条(默认值中性 / 值语义 / replaceSession 往返 /
重置覆盖全字段 / 保留 queuedTasks)。Swift 42 → 47。
2026-10-27 23:22:57 +08:00
..