proposal.md 3.68 KB

提案:历史记录列表增量渲染 + 视口懒加载(路线 A)

状态:第一阶段已完成(增量 prepend),第二阶段(Model/Delegate 重构)下周启动

Why

2026-04-24 当天应用在 Mac 上闪退 14 次。根因定位:

  • app(7).log 末尾戛然而止,无 Traceback
  • crash_log(3).txt 的 faulthandler 也没捕到任何信号栈
  • 两处都没栈 = 只能是 SIGKILL(faulthandler 无法拦截 SIGKILL,Unix 铁律)→ 99% 是 macOS jetsam 内存压力强杀

image_generator.py 历史记录渲染路径有三个相互放大的问题:

  1. 每次生成完图片都 refresh_history() 全量重建:2859:2884:4452:4507)。360 条 × 120×120 QPixmap + QIcon + QListWidgetItem,6 小时内累计创建 ~9 万个 Qt 对象,C++ 层回收滞后 + 内存碎片化。
  2. load_history_index() 每次调用都做 O(N) 路径修正,360 条 × 2 图 = 720 次 os.stat 在 UI 主线程上。崩溃前 11 秒内被 UI 事件重复触发了 7 次。
  3. QListWidget 架构决定"全部 widget 常驻",用户只看得到视口内 10–15 行,但 Qt 必须为所有 360 条持有 QPixmap。历史记录越多,风险越高。

代码注释已自承风险:# macOS 上触发 SIGKILLimage_generator.py:3071)。

What Changes

阶段 1:增量 prepend(已完成,2026-04-24)

历史记录是 append-only 数据(生成后 prompt/图片/参数不可编辑),生成完成后只需追加单条到列表首位,无需全量重建。

  • 新增 HistoryManager.load_history_item_fast(timestamp) — 只读 {timestamp}/metadata.json + 扫目录下 reference_*.png不触碰 index.json、不扫其他记录、不做路径修正
  • 抽出 _build_history_list_item(item) — 单条 widget 构建逻辑,供全量和增量共用
  • 新增 prepend_history_item(timestamp) — 增量插入到列表首位,异常时自动回退 refresh_history() 全量
  • 四处生成完成回调从 refresh_history() 切换为 prepend_history_item(timestamp)

阶段 2:Model/Delegate 视口懒加载(下周启动)

阶段 1 消除了"每次生成的 O(N) 重建",但"首次加载/手动刷新"仍是一次性画 360 个 widget。真正的根治是换架构:

  • QListWidgetQListView + HistoryListModel(QAbstractListModel) + HistoryItemDelegate(QStyledItemDelegate)
  • Model 只存 list[str] 的 timestamps(内存几乎为零)
  • Delegate 在 paint() 时按需读 metadata + 缩略图,滚出视口自动不再绘制
  • 效果等同于无限懒加载,用户零感知,不引入"翻页按钮"类的 UI 变化

额外优化:

  • QPixmapCache(LRU,默认 ~50 MB)缓存最近加载的缩略图
  • 后台线程(QThreadPool + QRunnable)异步加载缩略图,主线程占位 placeholder,缩略图回来再触发重绘
  • delete / clear 改为 model 级别增量(removeRow / reset),不再触发"全量 refresh"

Impact

  • Affected specs: history-list-rendering(新,阶段 2 时建立)
  • Affected code:
    • image_generator.py: HistoryManager / ImageGeneratorWindow 历史 tab 相关方法
    • 阶段 2 会新增 HistoryListModel / HistoryItemDelegate 两个类
  • User-visible:
    • 阶段 1:无感知(仅性能/稳定性提升)
    • 阶段 2:历史 tab 的选中样式、间距、hover 可能有 1–2 px 的视觉差异(QListView 默认样式 vs QListWidget),需要 QSS 调齐
  • Risk:
    • 阶段 2 触及历史 tab 核心交互路径(单击/双击/右键菜单/详情面板联动),需要完整回归
    • delegate 的尺寸计算(sizeHint)必须和现有 icon size + text 行高严格一致,否则滚动位置/选中高亮会错位