2026-05-30  添加 run.sh 启动脚本：自动创建/复用 venv、按需安装依赖、设置 PYTHONPATH 与 UTF-8 locale，支持 --reinstall / --test / -- 透传参数。
2026-05-30  TopBar 改为横跨整窗：主窗口外层布局由水平改为「TopBar(全宽) / [sidebar | stack]」垂直布局；sidebar 顶部不再额外留白，由全宽 TopBar 覆盖原来的左上空白。
2026-05-30  新增 .claude/settings.json 与 .claude/hooks/enforce-commit-flow.sh：Stop 钩子在存在未提交代码改动时阻止结束本轮，提示运行/测试/追加 Changlog/提交的完整流程。
2026-07-08  修复更新完成后更新列表未刷新的问题（store_facade.py）：update_one_async / update_all_async 的 finish 回调此前只调用 invalidate_cache() + data_changed，从未刷新 facade 自身的 _updates_cache，导致 get_updates_grouped() 仍返回含刚更新条目的旧列表；且 list_updates() 走「立即返回缓存」的非阻塞路径，失效后仍会拿到旧值。修正：为 _refresh_updates_bg 增加 force 参数，force=True 时先 invalidate_cache 再在 worker 线程中同步 _fetch_updates 拉取最新数据并写回缓存；两个更新回调改为调用 _refresh_updates_bg(force=True)，使已更新的条目从更新列表中去除。
2026-07-08  修复搜索到的已安装 RPM 包（如 rpm-build）点击安装失败且不显示原因的问题（store_facade.py、main_window.py）：根因是 _apply_installed_state 用搜索条目的 app_id（形如 "rpm:rpm-build"）去匹配按纯包名 key 的已安装列表，永远匹配不上，导致已装 RPM 仍显示「安装」按钮；点击后 PackageKit 返回 "already installed" 而安装失败。修正一：_apply_installed_state 对 RPM 条目额外按包名（package_name / 去掉 "rpm:" 前缀）匹配已安装列表，命中即标记 installed=True 并回填 backend/package_id/package_name，使已装包正确显示「打开」。修正二：main_window 将安装/卸载请求经 _on_install_requested / _on_uninstall_requested 包装，在 done 回调中用 QMessageBox 弹出后端返回的失败原因，不再只在下载页静默显示。
2026-07-08  修复搜索到的未安装 RPM 包（如 rust-gdb）点击安装弹窗报「安装失败」的问题（store_facade.py）：根因是 install_async 只用 self._as.get(app_id) 从 AppStream catalog 查条目，而 RPM 搜索结果（app_id="rpm:rust-gdb"）不在 catalog 里，返回 None → backend 判空 → 落到 else 分支直接返回 (False,"未知应用")，根本没调用后端（后端 WS 安装实测 ok:true 正常）。修正：install_async 查条目时回退到 _search_entries（与 get_app 一致）；backend 为空且 app_id 以 "rpm:" 开头时判定为 rpm；RPM 分支的包名优先取 entry.package_name，否则去掉 "rpm:" 前缀，避免把整串 "rpm:rust-gdb" 当包名传给后端。已在 VM 实测：install_async("rpm:rust-gdb") 返回 (True,'') 且包成功安装。
2026-07-08  拉取更新列表/检查更新时增加全窗加载遮罩，锁住交互避免假死观感（store_facade.py、main_window.py、widgets/loading_overlay.py、styles/*.qss）：数据拉取本就在 QThreadPool 后台线程执行、事件循环不会真卡死，但期间界面无任何反馈、按钮仍可点，导致用户误以为假死并可能重复点击引发并发操作。新增：StoreFacade.busy_changed(bool,text) 信号 + 引用计数（_busy_begin/_busy_end/_run_bg_busy），preload_data_async（启动）、refresh_metadata_async（检查更新/顶栏刷新）、_refresh_updates_bg（懒加载/更新后刷新）三处耗时读操作开始时发 True、结束时发 False，并发调用只在计数 0↔1 边界发信号，避免遮罩提前消失；新增 LoadingOverlay 控件铺满 central widget，显示不确定进度条+提示文案，并重写 mousePress/Release/DoubleClick/wheel/keyPress 吞掉所有交互事件锁住整个窗口；MainWindow 监听 busy_changed 显隐遮罩，resizeEvent 同步遮罩尺寸；QSS 深浅主题各加 LoadingOverlay/LoadingCard/LoadingLabel 样式。已在 VM 实测：启动时遮罩随 preload 显示、完成后隐藏，clean begin/end 周期信号与显隐均正确。
2026-07-08  修复启动后更新列表空白、需手动点「检查更新」才出现更新项的问题（store_facade.py）：根因是启动 preload_data_async 走 list_updates() 非阻塞路径——立即返回空缓存并起后台线程拉取；后台拉到后经 FlatpakClient 回调 _on_fp_cache_updated 只发了 data_changed，却没把最新 updates 同步到 facade 自己的 _updates_cache，而 get_updates_grouped 仅在 _updates_cache is None 时才重拉（空列表不重拉），于是列表一直空白，直到点「检查更新」走 _fetch_updates 同步路径才填上。修正：_on_fp_cache_updated 收到 key=="updates" 时先用 list_updates() 读回刚写入的新鲜缓存并赋给 _updates_cache，再发 data_changed，UI 随即显示更新项。已在 VM 实测：仅启动 preload、不点检查更新，_updates_cache 同步到 93 项，get_updates_grouped 返回 1 个 Flatpak 应用 + 92 个 RPM 包。
2026-07-09  彻底修复启动后更新列表仍为空（flatpak/rpm 均空）的问题（store_facade.py）：上一条修复在真实 GUI 里被并发时序覆盖——__init__ 的 prefetch_all_async（后台线程 A）拉到数据后经 _on_fp_cache_updated 同步了 _updates_cache，但紧随其后的 preload_data_async（线程池线程 B）的 job() 走非阻塞 list_updates() 拿到的是空快照，其 on_done 又无条件执行 self._updates_cache = res[1] 把列表清回空，B 通常晚于 A 完成，故最终恒为空。修正：新增 _fetch_updates_sync()（worker 线程内同步 _fetch_updates + 写回缓存），preload_data_async 的 job() 两个返回点改用它取代 list_updates()，使 on_done 写入的始终是真实数据，与 prefetch 时序无关。已在 VM 连续 3 次实测：完整 MainWindow 启动（含 prefetch+preload）后更新列表稳定为 93 项（1 Flatpak + 92 RPM），不再空白。
