Repository navigation
v4.8.0
下载:
FloatPulse-v4.8.0-win64.zip解压即用(便携版,数据留在程序目录旁)。
插件按需下载.fpplug,放进程序目录的plugin_store\即可在「插件中心 → 插件商店」安装。
校验完整性:sha256sum -c SHA256SUMS.txt。
[v4.8.0] - 2026-10-02
本版把 v4.7.0 之后两天的打磨一次收口,主线是三件事:图标体系与高 DPI 显示彻底对齐(自绘图标全量替换 emoji、软件图标随卡片真正缩放)、设置页体验重整(番茄钟独立成页、新增窗口透明度 / 界面缩放 / 小卡片图标大小)、插件生态与数据安全补强(应用内插件市场、数据写入滚动备份与损坏自愈、安装版数据目录双轨)。
Added
- 插件「密码保险箱」(v1.0.0,本版首次随发布包提供):只活在本机的密码保险箱——自定义字段收纳账号密码与任意机密,
Ctrl+Alt+V开主界面、Ctrl+Alt+B热键快速取用,复制后 30 秒自动清剪贴板。加密是 DPAPI + 主密码(PBKDF2)双因子整形库加密:换机器或没主密码都打不开,不联网、不同步、不碰宿主数据。★能力声明恒为空(capabilities: [])——它拿不到宿主任何数据接口,也拿不到网络库;vault_core刻意不 import PyQt6(纯逻辑可单测),搜索路径永不命中 secret 字段。上一版 Release 的附件区只有 5 个插件包,本版起为 6 个(vault.fpplug)。回归:tests/test_vault_core.py115 项 + 离屏tools/verify_vault.py48/48;README 插件表与marketplace/index.json(生成器tools/build_marketplace.py,5 → 6 条)同步 - 小卡片软件图标大小独立可调(用户可见变更,2026-10-02 用户报「图标大小调不动」):设置 → 外观新增「小卡片图标大小」步进器(
app_mini_icon_size,24–56px、每档 4px,默认 36)。根因不是主窗口——主窗口软件导航页的图标一直是card_size - 20严格跟随(离屏实测 card 60/96/140 → 图标位图 40/76/120),用户看到「调不动」的是悬浮球旁那张小卡片:card_window._refresh_app_page里btn_size = 76/icon_px = 36是两处独立硬编码,与设置项完全脱钩。现改为读配置驱动,并抽出唯一换算点constants.mini_btn_size()(按钮边长 = 图标 + 40;36 → 76,与历史值一致,老用户视觉零变化)。范围常量MINI_ICON_{MIN,MAX,DEFAULT}/MINI_BTN_PAD收进src/constants.py(三处共用:card_window 渲染、settings_panel 步进器、config_CONFIG_RANGES),避免两处各写一个数字日后漂移;上限 56 的依据是内容区可用宽 ≈ 440−48−40 = 352px,按钮 96px 仍排得下 3 列。接线:CardWindow.mini_icon_size懒读 property(set_config_manager在构造之后注入,不能放__init__;换源即作废缓存;越界钳制、脏值静默回退默认)+ 公开门面apply_icon_size()(D3 契约,宿主不穿透私有成员;可见且停在 app 页才重建网格,不可见只更值、切页自然生效,同值不重复重建以免长按 ± 时反复重排);MainWindow.mini_icon_size_changed信号 →knowledge_ball.main()转发到ball.card_window.apply_icon_size;「恢复默认设置」广播区补该键。回归:新增tests/test_mini_icon_size.py25 项(常量与换算单调/钳制、配置三件套、property 懒读/越界/脏值/换源、apply 门面幂等与可见性门控、源码级钉子「_refresh_app_page里不得再出现icon_px = 36/btn_size = 76字面量」、真实渲染三档 iconSize 与按钮边长、设置页落盘/广播/钳制/refresh 不误广播、MainWindow 信号 AST 断言);新增离屏tools/verify_mini_icon_size.pyA–G 七段全通过(三档实测 iconSize 24/36/56、按钮边长 64/76/96,deleteLater 结算后按钮数恒为 5 无残留;主窗口卡片链路回归 card=140 → 图标 120px);test_settings_nav搬运完整性参数表补_set_mini_icon_size;verify_settings_groups8/8(外观组加行后分隔线动态校验通过,脚本零改动);全量 pytest 2035 passed / 3 skipped / 6 subtests;ruff 全绿;真截图目检build/shots/mini_icon_{24,36,56}.png+mini_icon_settings.png(24px 一行 5 个 / 56px 自动换行 3+2,布局不溢出)。⚠本条的「主窗口图标一直正常」结论有误,已在下方 Fixed 段修正:当时只量了 pixmap 对象尺寸(card_size - 20),没量真实渲染像素;实机上是 QIcon 不放大导致图标恒卡在源帧尺寸(.lnk 更是恒定 40px),主窗口同样受影响 - 番茄钟设置独立成页(用户可见变更,2026-10-02 用户拍板):设置页左侧分类导航新增「🍅 番茄钟」分类(原「⚡ 全局工具」卡内的 4 项整体搬迁:总开关 / 专注时长 / 休息时长 / 自动进入休息),工具卡此后只留快速捕捉与截图钉屏两组热键。纯 UI 重组,零行为变更——控件属性名、
_on_pomodoro_*回调、refresh()同步与pomodoro_changed广播链一律不动,float_data/config.json的pomodoro_*四键口径与老配置零迁移。设置分类 9 → 10(SETTINGS_CATEGORIES新增("pomodoro", "🍅", "番茄钟"),插在 tools 与 system 之间);「全局工具」卡的末行标记随搬迁改挂到截图热键行(末行无悬空分隔线的不变量保持)。回归:test_settings_nav分类数断言 9→10 + 搬运完整性参数表补「番茄钟」注释分组;verify_settings_groups期望卡序同步(A/B 段 11 张卡逐卡校验通过,含「软件更新」子卡);全量 pytest 2035 passed / 3 skipped / 6 subtests;test_init冒烟全 [OK];使用说明页「设置」章节的分类清单与「全局工具」描述同步改写;light/dark 双主题真截图目检(build/shots/settings_pomodoro_{light,dark}.png,临时截图脚本跑完即删) - 插件中心卡片降噪(用户可见变更,2026-10-01 用户拍板「降噪保留 + 动作保持逐行」):对照用户截图诊断——卡片信息全平铺(描述两三行 + 📖摘要 + 动作逐行 + 依赖行 + 能力行 + 4 按钮)导致单卡过高、一屏只能看两张。降噪三项:①描述截断:卡面最多
DESC_MAX=64字(双列卡宽 ≈2 行,一眼能扫完),超出截断加省略号、全文进悬停提示(新增纯函数clip_text);②依赖行消失:requires(PyQt6.QtCore 这类开发者信息)不再占卡面行,连同被截断的描述全文一起挂进卡片整体悬停提示(QToolTip 沿父链找最近有提示的控件,悬停卡面空白处可见)——有无能力行行为一致;③能力徽章化:原「能力: 🌐 网络访问、✍ 写入数据…」文字行改为一排胶囊徽章(pluginCapBadge,短文案 🌐 网络 / ✍ 写入 / 🛠 改删 / 🧠 AI,完整语义在徽章悬停提示CAP_TIPS,从_make_card局部 dict 提升为模块级三卡共用),淡主色底 QSS 与pluginStoreBadge同族(light/dark token 双主题齐备)。附带:标题行「名称 v版本」拆成两个 QLabel(版本弱化为灰小字),商店卡 / 在线市场卡同步同款降噪(版本拆分 + 徽章行 + 描述截断,源包名/热键/体积保留文字行);「(无注册动作)」从主文字色(pluginCardDesc)降为弱化灰(pluginActionTag)不与描述抢层级。信息零删除——描述全文、依赖清单、能力语义全部可悬停查看;动作清单按用户选择保持逐行不动。回归:新增tests/test_plugins_card_polish.py15 项(clip_text 边界 / 长描述截断+悬停全文 / 依赖不占卡面且悬停可见(有无能力两态)/ 徽章数量短文案语义提示 / 标题拆分 / 商店卡在线卡同款 / 网格回归);既有test_plugins_panel42 项全绿(文本存在性断言不受排版影响);全量 pytest 1965 → 1987 passed;verify_plugins_panel23/23 +verify_plugins_page19/19;light/dark 双主题真截图(build/shots/plugins_card_{light,dark}.png,临时探针跑完即删);test_init冒烟 RC=0。二次收敛(同日用户复看反馈「介绍重复太多,使用说明看 md 就行」):卡面 📖 使用说明摘要行整体移除(描述与摘自 md 的摘要两段都在介绍「这插件是干嘛的」,重复感是主要观感问题;详情点「查看使用说明」走程序内 md 查看器,信息零丢失)——usage_summary()函数与USAGE_SUMMARY_MAX常量随之删除(无消费者不留死代码),theme.py 的pluginCardUsage死规则清理;描述再收短DESC_MAX64→48 字(约一行半,悬停全文保留)。测试同步:test_plugins_panel的摘要函数三测删除、「摘要上卡」断言反转为「摘要不上卡 + 按钮保留」,verify_plugins_pageC6 反转,test_plugins_card_polishDESC_MAX 常量断言对齐;复验:插件相关 54 项 + 全量 pytest(我方改动面全绿)+verify_plugins_page19/19 +verify_plugins_panel23/23 + 双主题截图build/shots/plugins_card2_*.png目检(单卡高度再降约 1/3)+ 冒烟通过 - AI 助手 1.12.0:新增「清空全部任务」动作 + 动作协议防谎报强化(用户可见变更,2026-10-01 用户实测反馈):用户实测「将任务清空,设置明天任务为…」时模型编造
delete_task #1/#2编号(数据里根本不存在)被 id 护栏全部安全跳过,却在正文谎称「已清空了所有现有任务」——任务实际没清空。三处修复:①新增clear_tasks{}动作(无参数、manage 级 → 走既有确认卡「【清空】全部任务(当前 N 条)」),执行层现取现删(不用校验时旧快照,确认期间数据变化无碍)、逐条删除收集撤销令牌(清空 N 条 = N 个令牌,结果卡仍可「↩ 撤销删除」一键恢复;_execute_and_report支持 list 令牌 extend)、部分失败如实报「成功 2/3 条」、空列表如实说「无需清空」;②协议规则补两条:清空/批量删除用 clear_tasks 不要逐条编造 delete_task;文字说明必须与动作一致——动作被跳过或失败时如实说明未完成,禁止声称已删除/已清空;③parse 校验豁免 clear_tasks 的 content 必填(无参动作)。契约注意:clear_tasks 是唯一无 id 的 manage 级动作(不进_OP_TARGET),原「manage 级 ⟺ 需要 id」契约测试同步加例外。回归:test_ai_assistant_actions60 → 67 项(parse 无参通过 / 描述带快照数量 / 执行层桩替身三场景——全成功返令牌列表、部分失败如实报、空列表诚实 no-op /_execute_and_report聚合令牌 / 协议含防谎报条款);全量 pytest 1965 → 1972 passed;ruff 全绿;已重打包plugin_store/ai-assistant.fpplug(1.12.0,37.5KB) - AI 总配置新增本地模型参数:思考模式 + 上下文长度(用户可见变更,2026-10-01 用户拍板:思考默认关 / 上下文默认 16K / 改参自动重启):设置 → AI 配置在本地模式下新增两行——「思考模式」(关=响应更快 / 开=输出思维链,
ai_local_thinking)与「上下文长度」(8K/16K/32K/64K 档位,ai_local_ctx_size),仅对本地 llama-server 后端生效(纯启动参数,ctx.ai.params()契约零变化,插件无感零迁移)。实现:config 三件套同步(bool 不进 RANGES;ai_local_ctx_size(2048,131072) +ai_server.sanitize_ctx_size双层收敛);build_launch_args(gguf, port, ctx_size, thinking)参数化;新增AiServerManager.restart()状态驱动平滑重启(运行中改参 → stop 后等旧实例真正退出(finished 信号)再以新参数拉起,替代固定延迟猜测——Windows 下 terminate 对无窗口控制台程序可能迟至 3s 强杀才生效;重复调用以后一次参数为准,期间被手动拉起则丢弃 pending 防双启);设置页_ai_start_local统一保存/手动启动入口(参数未变不打断、变化自动重启并提示);两行随本地/云端模式显隐同步(_ai_apply_mode_ui补齐)。★参数选型实测存档:--reasoning-budget 0(预算立即耗尽)不被 Qwen3.5 模板遵循——同题对照实测 b10690:budget 0 与默认一样吐完整思维链(384 tokens 耗尽、content 为空、5.7s),--reasoning off才是真开关(直答 0.3s/14 tokens,快约 20 倍),故「关」实现为--reasoning off。回归:test_plugin_ai扩至 29 项(新键类型/范围/bool 不进 RANGES、launch args ctx 收敛与 reasoning 旗标有无、restart 直启/覆盖/防双启守卫);verify_settings_groups8/8(AI 卡加行后动态校验零改动通过);verify_ai_backend26/26(B3/B4 补两新行显隐断言;顺带修正过期的 C1——weekly-report 自 v1.1.0 声明 ai 能力后属合法候选,反例换真零能力的 kb-search);全量 pytest 1965 passed / 3 skipped / 6 subtests;本地模式 AI 卡 light/dark 双主题真截图目检(build/shots/ai_local_card_*.png,临时脚本跑完即删) - 设置新增「窗口透明度」(用户可见变更,2026-10-01 用户提出):设置 → 外观与主题新增「窗口透明度」步进器(
window_opacity,50–100%、每档 5%,默认 100=不透明)——主窗口整体半透明,方便「盯参考/对照材料」时让窗口退到背景(悬浮球、便签、卡片窗不受影响)。实现:config 三件套同步(int +_CONFIG_RANGES(50,100),下限 50 保文字可读——QtwindowOpacity=0时窗口命中测试直接穿透不可点击);主窗新增_target_window_opacity()(百分比→0.5~1.0 收敛,垃圾配置回 1.0 的防御纵深)与_apply_window_opacity()(setWindowOpacity单点应用,不走全量主题刷新);入场淡入终点改为读配置目标(_play_entrance_animation原硬编码setEndValue(1.0),预设半透明时启动动画直接到位,不闪「先不透明再变透明」);设置页 Stepper 改动即时持久化并直调主窗应用(getattr callable 守卫);「恢复默认设置」广播区补透明度回 100% + Stepper 同步刷新(refresh 走 blockSignals 护栏)。回归:新增tests/test_window_opacity.py17 项(config 三件套越界/错型拒写、垃圾加载回退、目标值收敛含防御纵深、真窗 show 后入场淡入终点=配置目标 70%≈0.7、Stepper 夹取/落盘/直调/同值省写、host 缺方法不崩、恢复默认回不透明且控件同步);全量 pytest 1948 → 1965 passed;verify_settings_groups8/8(外观组加行后分组结构 B 段动态校验通过,脚本零改动);test_init冒烟全 [OK] - 悬浮球右键菜单分组收纳(用户可见变更,2026-10-01 用户拍板):6 个插件动作此前全部平铺在一级菜单(站内搜索 / 周期任务 / 立即检查周期任务 / 密码保险箱 / 快速取用 / 生成日报周报草稿),装几个插件菜单就长一截。现参照 Win11 桌面右键「新建 >」的层级(用户提供的样图)把全部插件动作收进「🧩 插件功能」子菜单:一级菜单定型为「打开主窗口|开始专注|🧩 插件功能>|✂ 截图钉屏|退出程序」五段(分隔线分组不变,「截图钉屏」按用户选择留一级直达),二级保留各动作图标与标题、随启用/停用动态增删(
refresh_plugin_menu既有入口不变);子菜单显式套同一份get_menu_qss(与托盘「📌 便签」子菜单同款做法),light/dark 跟随宿主主题(ball.apply_theme正规换肤链覆盖)。回归:verify_plugin_loader35/35(E1/E2 期望列表更新为子菜单形态 + 新增 E2b 子菜单收纳断言;F1/G2 计数同步);verify_weekly_report65/65(D/E 段改走子菜单取 QAction);全量 pytest 1948 passed;test_init 冒烟全 OK;light/dark 四张真截图验证菜单结构与换肤(build/shots/ball_menu*,临时探针跑完即删)。★顺带对齐verify_plugin_loader三条过期断言(只改脚本不改产品):D1「.fpplug 自动解压后加载」→「残留 .fpplug 不再自动解压」(双目录模型 2026-09-27 移除自动解压后脚本一直没跟上,C4/F1/E2b 连锁红灯同源) - AI 助手思考动画自绘化(v1.11.0,用户可见变更):请求在途的「打字中」三点气泡从
●○○字符帧轮换(180ms/帧,4 帧往返) 换成 自绘连续动画控件ThinkingDots——①零字体依赖:●○几何符号在 msyh 下与▶▾▸同属supportsCharacter=False高危区(图标自绘铁律的既判场景),改为paintEvent画三个圆点后彻底摆脱字体;②观感:三点按正弦相位做垂直弹跳 + 透明度呼吸(33ms/帧 ≈30fps、周期 900ms、三点各差 1/3 相位,只取正半周弹跳落基线平滑),比 4 帧跳变顺滑;③主题跟随:颜色取宿主主题$primary并订阅theme_changed(IconButton/PageTitle 同款 callable 守卫),换主题即时换色;④减弱动效联动:设置「减弱动效」开启时不启动计时器,静止显示三点(相位取 0.125 让透明度呈阶梯,不呆板);⑤气泡壳chatBubbleThinkingQSS 不变,同步删除已无消费者的chatThinkingDots文字规则(theme.py)。接线:页面级_think_timer/_tick_thinking/_THINK_FRAMES删除(计时器归控件自管,_hide_thinking显式stop()双保险)。回归:新增tests/test_ai_thinking.py9 项(尺寸/启动即动画/stop 停/相位推进/离屏渲染非空/减弱动效静止但仍渲染/无宿主取色兜底/theme_changed 换色/页面挂气泡幂等+靠左+拆除计数回落+发送全链路在途挂起回复到达拆除);verify_ai_assistant121/121(C20b/C20c 断言对齐新实现;顺带修正过期的 A7——weekly-report 自 v1.1.0 声明 network+ai,「未声明」反例换真零能力的 kb-search);全量 pytest 1820 passed - 五插件第二批增强(2026-10-01,用户可见变更):首批「推荐项」之外的五个可选批次一次做完,版本齐升 —— ①AI 助手 1.10.0:会话导出 Markdown(会话栏「💾」→ 选路径落盘 utf-8+LF,逐轮问答原样导出,快捷指令只导界面文案不含数据块,空会话拦截)+ 自定义快捷指令(快捷行「+」弹小对话框添加,上限 6 条随 config 落盘,点击直发不附加数据块,右键删除)。②AI 文本工坊 1.5.1:源文超 8000 字在「处理中…」状态行明示「已按上限截断发送」(此前悄悄砍内容)。③站内搜索 1.2.0:搜索历史(最近 10 条存插件私有目录,输入框为空时显示「最近」行,点击重搜并置前、右键删单条、可清空;读入消毒+原子写+坏文件降级)。④周期任务 1.2.0:周末顺延(规则可勾「周末触发日顺延到下周一」——周六/日触发改到下周一同时刻生成,双周末日顺延撞同一周一自动合并为一条;
rule_fires_shifted谓词换入latest_fired_day/next_fire_at,老规则缺省行为逐日等价零变化)+ 规则导出/导入 JSON(import_rules逐条 validate_rule、rule_id 撞车跳过现役优先、坏条目计数、MAX_RULES 照守)+ 规则卡片显示上次生成的任务标题与时刻(last_task随_fire落档、编辑透传不丢)。⑤日报周报 1.2.0:段落开关(预览区「任务/碎片/笔记」三勾选即时重生成,build_report新增include参数缺省 None=与旧版逐字节一致,任务勾选同时控制完成+未完成两段,专注统计恒保留)+ 导出历史(另存/写 vault 成功记入最近 10 条,exports.json原子写,footer 新增「🕘 最近导出」菜单点击打开所在文件夹、可清空;既有五按钮文案未动,verify F6 保持全绿)。回归:全量 pytest 1743 → 1820 passed(新增test_ai_assistant_export22 /test_kb_search_history15 /test_recurring_enhance19 /test_weekly_v12018 + workshop/kinds 各 +3);verify_weekly_report65/65;5 个 .fpplug 已重打包;test_init冒烟全 [OK]。★顺带修掉test_kb_search_kinds的 QApplication 裸调用雷(单跑 RC=127 硬崩,此前靠组合跑掩盖)——_APP留存引用,项目铁律再现 - 五插件优化增强批次(2026-10-01,用户可见变更):按「推荐项」范围对 5 个内置插件各做一轮增强,版本齐升 —— ①AI 助手 1.9.0:会话持久化 + 多会话(成功问答自动存
sessions.json,原子写、单会话 100 条/全局 15 个上限,重启恢复上次会话;页顶下拉切换历史会话、+新开、🗑删除(最后一条=清空内容);提示气泡与动作结果卡不落档,恢复只重现问答主线;坏档降级保留原文件)。②AI 文本工坊 1.5.0:结果历史(最近 20 条存history.json,「🕘 历史」卡点击回填结果区可再落库,「提取待办」历史回填后转任务按钮照常可用)+ 自定义指令收藏(「★ 收藏指令」存成按钮、去重 10 条上限、点击填入右键删除)。③站内搜索 1.1.0:数据源范围过滤(搜索框下五源勾选默认全开,全开=与旧版逐条同路径;kb_search_core.search的kind参数升级为可接受集合——分数在全库算好后按范围排除,多选排序与不过滤一致)+ 跳转自愈(碎片/笔记跳转前用同一份只读快照模拟目标面板的子串过滤(与宿主_frag_search/_note_search同口径),bigram 交叉召回必空时改跳空关键词只切页并 toast 说明,消灭「跳过去列表是空的」)。④周期任务 1.1.0:新增「每 N 周」规则类型(nweekly:间隔周数 1–52 + 星期几多选,锚点=创建当周的周一、周索引按周一对齐,编辑保留原锚点周期不漂移;老规则文件零迁移)。⑤日报周报 1.1.0:一键 AI 润色(「🧠 AI 润色」把当前草稿交 AI 总配置润色回填预览;手动编辑过的草稿先确认再覆盖;manifest 能力从只读升为network+ai,只在点击时刻发请求、只送草稿文本)。回归:全量 pytest 1687 → 1743 passed(新增test_ai_assistant_sessions16 /test_workshop_history_ui10 /test_kb_search_kinds15 /test_weekly_polish12 / recurring+workshop 纯逻辑扩充);verify 全绿——ai_assistant 121/121、text_workshop 34/34、kb_search 55/55、recurring_tasks 46/46、weekly_report 65/65、plugin_p0p1 37、plugins_panel 23/23、plugins_page 19/19;5 个 .fpplug 已pack_plugin --force重打包(plugin_store/)。⚠verify_plugin_loader出现 6 个红灯,经查为并行会话未提交的托盘/崩溃标记改动(knowledge_ball/tray 中间态)所致,与本批次无关(隔离探针:失败点全在其自有 fixture 与球菜单组合,不触及任何本批改动文件)
Fixed
- 软件图标不随卡片缩放(用户第二次反馈,根因是 QIcon 从不放大):上一轮加了「小卡片图标大小」设置项后,用户截图指出主窗口软件导航页同样有问题——卡片框跟着设置放大了,里面的图标纹丝不动。自查发现上轮只量了 pixmap 对象尺寸(
card_size - 20,看着是对的)而没量真实渲染像素,结论不成立。真机取证(真实 Windows 平台 / dpr=1.25)定位两层根因:①**QIcon.pixmap()从不放大**——源帧小于请求尺寸时原样返回;②对**.lnk快捷方式更极端:请求 40/76/108/120 一律只回 40×40(shell 给的 32px 图标 ×1.25),而availableSizes()谎报有 320 帧、actualSize(108)也谎称能给 108 → 上游无从察觉。真实平台实测 card 60→140 图标可见宽恒定 39→39(跨度 0),与用户截图完全吻合。修复两条:①新增src/win_icons.py——用 stdlib ctypes 调 shell jumbo 图像列表(SHGetImageList(SHIL_JUMBO)+IImageList::GetIcon,vtable 下标 10)拿 256×256 真高分图,经GetIconInfo+GetDIBits转 QImage(★GetDIBits必须给 memory DC,传 NULL 直接失败——踩过;GetIconInfo新建的两份位图由调用方DeleteObject回收,DestroyIcon同理);这条路径连.lnk也能出大图**,顺带修掉「快捷方式显示成通用箭头图标」的观感问题。②新增_fit_icon()尺寸收口(extract_exe_icon与load_icon_pixmap共用,口径唯一):把任意来源位图归到「逻辑边长 == 请求值」,dpr>1 的(QIcon 用 dpr 凑逻辑尺寸)原样保留不重采样,dpr==1 的按屏幕 dpr 补物理像素避免高 DPI 发虚。修复后真实平台实测 icon 38→116(跨度 78),逐档核对「卡片 −20 = 图标」。健壮性:win_icons非 Windows / API 缺失 / 取图失败一律返回None回落 Qt 路径且永不抛异常,结果按规范化路径缓存(含失败结果,缓存命中 0.007ms,拖尺寸步进器不会每档重跑 shell)。回归:新增tests/test_app_icon_scale.py20 项(_fit_icon放大/缩小/已正确不重采样/高 dpr 源保留/空位图/0 尺寸;extract_exe_iconQt 兜底也收口、shell 优先且收口、缓存命中、源码级钉子「shell_icon_image + _fit_icon 不得被删」;win_icons脏输入不抛/缺文件返 None/真实 exe ≥128px/缓存同对象);新增离屏tools/verify_app_icon_scale.pyA–E 全通过(端到端四档 visible bbox 38/72/103/114 单调增、跨度 76,修复前恒 0);全量 pytest 2035 → 2055 passed / 3 skipped / 6 subtests;ruff 全绿;真实 125% DPI 对照真截图build/shots/icon_scale_{before,after}.png(前:四档恒定 40px 且带快捷方式箭头;后:40→76→108→120 且是软件真图标) - 细节优化与 bug 排查批次(2026-10-01 晚,全面排查):①侧栏拖宽重启即丢(用户可见)——
side_bar_width被_save_side_width读写落盘、却从未声明进 config 三件套,而_load()只认DEFAULT_CONFIG的键 → 每次重启宽度静默回 168;现补齐三件套(默认 168 / int / (140,280) 与SIDE_BAR_MIN_W-MAX_W对齐),test_restore_from_config语义同步(越界值配置层拒绝内存保持合法值 + UI 层_clamped_side_width兜底夹取双层都钉死)。②AI 服务停止的 3s 强杀定时器会误杀新实例——stop()后旧实例 3s 内退出、restart()拉起新实例,过期定时器判self._proc把新实例 kill 掉(Windows terminate 快速生效时 restart 路径必触发);改为闭包捕获当时那个旧实例,只对它操作(test_stop_kill_timer_tracks_old_instance三场景钉死)。③AI 服务启动失败状态机卡死——exe 缺失等FailedToStart错误进程不会发 finished,_proc永不清空 → 之后start()全被「已在启动/运行中」守卫拒绝,只能重启程序恢复;现_on_error_occurred就地清_proc+ 停探活复位状态机(Crashed 等错误仍等 finished 清理,语义不变;test_error_occurred_failed_to_start_resets_state_machine钉死)。④第二实例唤醒静默失效——唤醒后台线程(无 Qt 事件循环)里QTimer.singleShot(0, cb)永不触发(offscreen 探针实测 FIRED=False),第二实例拉起主窗功能一直没生效;改主线程 QObject 信号桥(跨线程 emit 自动排队回主线程,探针验证 FIRED=True IN_MAIN_THREAD=True)。⑤页后台预热异常会崩整个进程——_warm_one_page是 QTimer 槽,PyQt6 对槽内未捕获异常默认 qFatal 中止;预热属锦上添花,现单页失败仅 warning 留痕(切页时会再走一次构建)。⑥托盘右键菜单悬空隐患——setContextMenu不接管所有权,QMenu靠子菜单闭包引用环碰巧续命;显式self._tray_menu持有。⑦五处静默吞异常补日志:AI 探活输出订阅者回调、剪贴板碎片写入(文本/路径)、全局热键回调(此前表现为「热键按了没反应」无从排查)、config 写盘失败;顺带修正ai_local_thinking过时注释(--reasoning-budget 0 → --reasoning off)。⑧ai_server_on_finished的del was→was.deleteLater()(finished 信号发射途中丢最后引用,防 wrapped-object-deleted)。⑨信号桥落地漏写信号声明(⑧修复的真机回归,用户截图反馈)——_WakeupBridge类体漏了woke_up = pyqtSignal()类属性,第二实例唤醒路径运行期AttributeError;补回声明并新增tests/test_wakeup_bridge.py2 项护栏(AST 静态断言信号声明+槽方法+QObject 基类缺一不可、按真实源码 exec 出类实例化 connect→emit 全链路;反向验证:内存构造漏信号的坏源码必触发断言失败)。回归:全量 pytest 1988 passed / 3 skipped;ruff 全绿 - 小卡片 Tab 快速连点「键与页错位」(移除页间滑动转场,用户可见变更):小卡片左侧图标栏快速连点时,选中键与显示页可能永久错位(如截图:task 键亮着、页面停在临时笔记)。根因是页间 260ms 滑动转场的结构性缺陷——转场期间真实 QStackedWidget 停在源页索引且被隐藏(动画结束才 setCurrentIndex),
_switch_mode每次读stack.currentIndex()当转场起点,连点即读到过期索引:第二击点回转场源页时走「同页直切」分支、旧转场 finished 稍后仍把 stack 设成旧目标页 → 键亮 A 页显示 B,且不再自愈;即使不错位,第二段转场也从过期源页起跳产生视觉跳变。该快照合成转场已有三代实现、两代翻车史(淡入穿帮/双层玻璃发白,docstring 有存档),修补成本高于收益,按用户授权整体移除:删_animate_page_transition/_page_content_snapshot/_finalize_page_transition/_TransitionCanvas(约 200 行),_switch_mode收敛为「选中态 + 指示器滑动 + 即时 setCurrentIndex」——页面即切、指示器仍带 260ms 滑动余韵,错位从构造上不可能发生;驱动旧转场的回归脚本tools/check_transition.py一并删除。回归:新增离屏护栏tools/check_tab_rapid_switch.py5/5(沿真实按钮信号路径复现旧错位序列 fragment→note→task→fragment 中途不泵事件,断言 stack/checked/_last_mode/可见性四方一致 + 全页轮巡两圈逐点一致 + 同页重复点击幂等);全量 pytest 1687 passed / 3 skipped 不变,test_init冒烟全 [OK] - 碎片「钉为便签」不再自动收录笔记(用户可见变更):碎片页右键「📌 钉为便签」此前复用转存公共路径——先
add_note()建一条笔记再钉它,每次钉便签笔记管理就多一条「💾 xxx」记录,污染笔记库(用户拍板取消)。现便签新增kind="fragment"直接锚定碎片:内容写回 fragments.json、不经过笔记;碎片无标题字段 → 标题取内容压平前 20 字、右键无「标题重命名」、删除项为「🗑 删除碎片并关闭」;孤儿清理照常(碎片被删 → 便签自动关闭、几何记录清掉)。笔记「钉到桌面」/任务「钉为便签」不受影响(本就锚定已有数据)。接线:StickyNoteManager/StickyStore增加fragment_manager/fragment_ids注入(None = 碎片便签不可用,旧构造点零改动),宿主构造点补传;_to_note_id公共转存路径保留给「💾 存为笔记」。回归:verify_sticky_notes新增 H 段 7 项 → 29/29(H2 ★ 直钉后笔记数不变 / H5 编辑写回碎片 / H6 标题随内容刷新 / H7 删碎片孤儿关窗);全量 pytest 1782 passed(全量中两处瞬时红灯test_kb_search_history/test_ruff_clean单跑与手动复跑全绿 = 并行会话写文件竞态,非本轮改动面);test_init冒烟全 OK
Added
- 首启三步引导(3.4):新增
first_run_done与src/onboarding.py——首启 800ms 后弹出三步向导(① 热键速查:Ctrl+Alt+K 快捕 / Ctrl+Alt+S 截图钉屏 / Ctrl+K 站内搜索需装 kb-search 插件 / Esc 关卡不退出;② 悬浮球玩法:贴边吸附、悬停弹卡、拖文件进碎片池、拖 exe 建启动器、关窗收托盘;③ AI 可选说明),文案口径逐条对照 README 与实际注册热键,Esc/跳过即完成不再骚扰;设置页「🔄 重看引导」可随时重看 - 界面缩放(3.5):设置外观新增「界面缩放」85–150% 档位(
ui_scale),全局字号按round(BASE_FONT_PT × 比例)即时重设并走主题广播链,不重启生效;刻意只缩字号不缩 px 布局(低风险取舍,85–130% 观感最佳),QSS 内 75 处写死 px 字号不随缩放(已列清单为遗留) - 大列表窗口化渲染(3.6):新增
src/list_windowing.py分块建行助手(首屏 120 行 + 滚到底续载),接入碎片工作台与日程任务的 refresh(≤200 行全量直建与旧路径逐字节等价);500 条实测 refresh 3.3–3.7ms(全量直建对照 7.3–7.8ms),筛选/搜索/选中恢复语义不变 - 数据安全三件套(成熟化批次):① config.json 损坏不再静默丢设置——先备份
.corrupt.bak再回退默认,启动时托盘告知(此前except: pass无痕迹回退,热键/AI key/主题全丢);② 全部数据 JSON 写前滚动备份(float_data/backups/每日一份保留 7 份,失败不阻断保存)+ 顶层data_version版本号与迁移层(json_store.STORE_VERSIONS/MIGRATIONS,存量文件零迁移);③ 6 个数据 Manager(fragments/notes/tasks/groups/stickies/assets)统一走save_records骨架(写前备份+版本号+原子写,fragments 保持紧凑格式) - 插件网络桥 SSRF 防护闸(
src/net_guard.py):协议白名单(仅 http/https)+ 本地主机名黑名单 +getaddrinfo解析全量私网判定(回环/私网/链路本地/保留/组播/未指定,IPv4-mapped IPv6 先还原再判)+ 解析失败 fail-closed;拒绝结果与既有_deny同构并进审计日志。本地 AI 不被误伤:宿主经ai_server.sync_loopback_allowlist把 llama-server 端口与回环 base_url(Ollama)登记进回环白名单(启动 + 设置页保存时各一次),未登记的回环/内网一律照拒;set_allow_private_network留作后续高级开关 - 安装版数据目录双轨(2.2):zip 便携版(包内含
portable.marker)数据随 exe 走、行为与历史一致;安装版(frozen 无 marker)数据走%APPDATA%\FloatPulse(APPDATA→LOCALAPPDATA→exe 目录三级兜底),首启检测旧目录有数据时弹一次询问迁移(整树复制+数量校验,旧目录改名float_data.migrated-<时间戳>留备份,绝不删除);源码运行模式行为不变(app_paths.resolve_data_root纯函数 + 模式矩阵测试);logger 与 TempAssetManager 在安装版跟随数据根,不再写进安装目录 - 更新被动提示(2.3):启动 15s 后静默检查(默认每 7 天,设置页「自动检查更新」可关;只访问 GitHub Releases API,不自动下载、失败静默),发现新版托盘气泡 + 关于页版本旁「(有新版本 vX.Y.Z)」
- 主题「跟随系统」(3.1):
theme新增follow值,经QStyleHints.colorScheme解析并监听colorSchemeChanged即时全链路重刷(主窗/球/卡片/便签/快捕条/截图层);设置页外观三态(浅色/深色/跟随系统);老版本 PyQt 缺 API 时安全回落深色 - 崩溃可感知(3.2,后续按用户反馈瘦身):日志新增成对的会话标记(
[会话] 启动 vX.Y.Z/[会话] 正常退出)圈出会话区间,供人工排障定位会话边界;设置页「关于」新增「📂 打开日志」直达数据目录(启动时的「上次可能异常退出」气泡已移除,见 Removed) - 首次关窗收进托盘提示一次(3.3):主窗口 closeEvent 收进托盘时托盘气泡说明「程序仍在后台运行」(
tray_hint_shown防重复,信号分层不改分层纪律) - 发布工程三件套(2.1/2.4/2.5):ISCC 进 CI(choco 安装 Inno Setup,
FP_ISCC_PATH注入,找不到时软跳过不崩)→ Release 自动产出 setup.exe;发布前「三源对齐」自检(tag ↔app_version.py↔ CHANGELOG 标题,不符即终止发布,tools/check_release_consistency.py可本地复用);产物附SHA256SUMS.txt(zip/setup.exe/全部 fpplug,GNU 格式可sha256sum -c);新增 GUI 夜检 workflow(workflow_dispatch手动档,run_gui_check.py --only跑 7 个精选离屏脚本) - CI 加 ruff 零告警闸门 + pip-audit 信号位(4.1):ruff==0.16.9 写死进 tests.yml,规则只查正确性(E4/E7/E9/F/W605,不开风格类);全仓一次性清零 114 处机械违规(F401 未用 import 90 处为主,全部零行为变化,pytest 全绿背书);pip-audit 非阻断任务先给信号(优化清单 3.5 落地)
- 应用内插件市场「🌐 检查在线市场」(按需联网,与更新检查器同一立场):插件商店弹窗新增「🌐 检查在线市场」按钮——点击那一刻向 GitHub API 发两次 GET(仓库
marketplace/index.json索引经 contents 接口取 raw +/releases/latest解析附件 asset id),列出「远端有而本地未装」的插件卡片(名称/版本/描述/能力/热键/体积),点「⬇ 下载安装」即经 octet-stream 通道下载 .fpplug → sha256 校验(索引带哈希,不符即丢弃并提示,绝不把坏包放进商店)→ 原子落盘plugin_store/(临时文件 +os.replace,同名旧版本源包被替换但不碰已安装目录)→ 委托既有_on_install链路(rescan / 菜单重建 / 商店刷新 / 结果反馈全覆盖)。失败分级文案:403 → API 限流提示、404/超时 → 对应可重试提示、其他 → 「无法连接 GitHub,离线不影响任何本地功能」;不做静默升级——已装插件不出在线卡,本地版本低于远端时只提示「可更新」(先卸载再装),绝不覆盖用户已装目录;busy 期间重复点击被忽略。基础设施:src/plugin_market.py纯逻辑模块(刻意不 import PyQt6:索引解析逐条消毒——id 过is_safe_plugin_id、file 强制纯文件名防路径穿越、sha256 格式校验、size 正整数;已装状态分流复用parse_version宁漏报不误报;http_get_bytes二进制下载与 plugin_net 同契约永不抛异常 + SSRF 闸);plugin_net.py新增二进制 GET 通道(http_get_bytes+make_async_bytes_getter,与文本 GET 同一条 relay 管道)——二进制不能走 utf-8 replace 解码,单独实现。索引生成器tools/build_marketplace.py(扫plugin_store/*.fpplug出marketplace/index.json,manifest 缺 description 退回使用说明首段;--check模式可进 CI)——索引只存文件名不存 asset id(每次发版 id 都变),运行时按文件名解析,插件增改时重跑一次即可,发版不用动。回归钉子tests/test_plugin_market.py(27 项:索引解析逐条坏只丢那条 / 已装分流 / 附件解析 / 本地 HTTPServer 实测下载 200/404/超限/SSRF 拦截 / sha256 / 落盘原子替换与穿越拒绝)+ 离屏端到端tools/verify_plugin_market.py(10/10:本地伪造「索引+Release+附件」三件套走真实点击路径——出卡、下载安装全链路、sha 拒收、缺附件、403/404 分级、已装过滤、busy 防重,light/dark 双主题真截图)
Removed
- 启动时「上次可能异常退出」托盘气泡(3.2 的提示半边,用户可见变更):按用户要求移除。根因不是误报——日志取证(2026-10-01)显示判定口径正确(
[会话] 启动后无[会话] 正常退出即真),但当天 6 次启动前的退出全部是强杀(开发/调试场景 IDE 停止 / Ctrl+C / 杀进程是常态,历史 61 次 aboutToQuit 正常触发均为日常使用日),导致判定恒真、每次启动必弹、沦为纯噪音。保留:成对[会话]标记仍写入日志供人工定位会话边界;设置页「📂 打开日志」入口不动;config 损坏重置告知气泡不动。改动面:tray.notify_startup_once()去参(只保留 config 损坏告知)、logger.previous_session_abnormal()与 16KB 尾窗扫描删除、启动装配处(knowledge_ball)不再判定;test_crash_marker.py收敛为纯标记写盘/收尾排序契约(3 项),test_tray_controller.py启动告知测试同步(corrupt 单气泡 / 正常零气泡)
Changed
- 启动闪屏玻璃化 + 悬浮球品牌图形(用户可见变更):启动第一眼的闪屏此前是「实底白卡 + 细边框 + 干巴巴的旋转弧」,与全应用玻璃拟态语言割裂(便签玻璃化同因)。现整体重绘——①玻璃化本体:
glass_fill半透明填充 +primary_border描边 + 顶部 1px 内高光,light 白玻璃 / dark 深蓝黑玻璃随启动主题;token 的函数式 rgba() 串 QColor 不认 → 内置_css_color小解析器(第 4 参 >1 按 0-255、≤1 按比例,失败回退兜底色绝不画黑;与便签同因同款);②品牌图形 = 悬浮球:替代裸旋转弧——主题色径向渐变球体(高光偏左上 30%)+ 白色高光点,球体随帧计时器正弦上下浮动 2.5px(周期 1.5s),底部影子随升降缩放淡出(悬浮感的关键参照物),外围保留旋转弧(_angle帧驱动口径不变);③入场淡入 160ms(OutCubic;与 300ms 淡出共用 windowOpacity,finish 时先停入场动画防打架);④主窗出场动画调优:上移 10px→16px、260ms→320ms(OutCubic 不变),「浮起来」更明显且与品牌契合。闪屏仍是 300×170、show 不抢焦点、12 阶段文案照常。回归:verify_splash11/11(D1 比色口径随玻璃化同步:取色点下移避开标题文字、底色断言 card_bg_solid→glass_fill、alpha ≥190 容两主题;A/B/C/E 契约零改动);test_init冒烟全 [OK];全量 pytest 我方改动面全绿(2 红灯test_ruff_clean/test_plugin_ai经 mtime 取证为并行会话对 settings_panel/ai_server 的实时编辑中间态,与本轮无关);双主题真截图build/shots/splash_{light,dark}.png - 主窗口启动懒加载 + 后台预热(用户可见变更,启动 2.47s → 1.06s):日志
[启动]分步打点取证显示总启动 2.47s 中「构建主窗口界面」占 2079–2250ms(≈90%,其余各段毫秒级;「加载插件」仅 31ms 不是瓶颈)——根因是 10 个页面在主窗构造期全部同步构建(设置页/知识库/软件导航等重页即使用户从不点开也照建)。现改懒加载 + 预热:首页(0)仍同步构建保证首屏即现,页 1–9 用空占位 QWidget(objectNamelazyPagePlaceholder{idx})占槽;10 个页属性全部 @Property 化(首次访问经_ensure_page_built幂等构建并以「insertWidget 到同槽位 + removeWidget 占位」替换,物理索引不变 → 插件页 10+ 起始契约与全部外部引用零迁移),_switch_page入口先 ensure、refresh_page只按需访问可见页属性;主窗 show 后 QTimer 40ms 逐拍后台构建一页(预热),用户点开任何页时页面已就绪、换页无感。apply_theme只刷已构建页(未构建页构建时自会用当前主题)。三个必踩坑全踩全修:①builder 表漏填 key 0 →stack.addWidget(None)直接 0xC0000005 段错误(run_gui_check 子进程崩溃丢 stdout → 裸跑探针取证);②refresh_page的 panel 字典字面量一次性求值全部页 property → 构造期强推全页构建 → TasksPanel 构造内再触发 ensure 无限递归(CPython 把递归深度耗尽转译成误导性的AttributeError: no attribute '_page_tasks')——修复=字典改按需 if/elif +_ensure_page_built加_lazy_building重入守卫(重入返回 None,调用方已判空容忍);③QStackedWidget没有replaceWidget(那是 QLayout 的 API)→ 等价「先 insert 再 remove」并守住 currentIndex 不被替换动作扰动(预热期间用户看到的页面纹丝不动)。效果(test_init 真实启动口径):构建主窗口段 2079–2250ms → 515ms,总启动 2.47s → 1.06s(-57%)。回归:全量 pytest 1943 passed / 3 skipped / 6 subtests 全绿(较上轮 1782 多出的 161 项为并行会话同日新增,非本轮改动面);verify_nav_groups/verify_settings_groups8 项 /verify_empty_state16 项(页属性重依赖护栏)全过;test_init冒烟 RC=0 全 [OK](含 0–5 页切换与双主题切换) - 动效 token 与时长口径下沉(UI 强化方案 A1 / 阶段 1):新增
src/motion.py(刻意不 import PyQt6,与nav_layout/md_export/app_version同款约束 → pytest 可直跑,无需 offscreen):MOTION五个语义档位(instant 0 / fast 120 / base 180 / slow 280 / stagger 18,后三个与既有NAV_DROP_MS/NAV_GROUP_EXPAND_MS/NAV_GROUP_STAGGER_MS同值)+EASE三个缓动名(字符串键而非QEasingCurve.Type枚举,调用侧自行getattr,以保持模块零 Qt 依赖)+duration(ms, speed)/eased_ms(kind, speed)。收敛三处重复实现:card_window、tasks_panel、main_window._nav_anim_ms原先各写一份ms / speed缩放式(main_window的注释还专门写着「与任务面板保持一致」,实际一个用round一个用int),现全部改调motion.duration——唯一口径max(0, int(ms / speed)),档位非法/非正回退中性档 1.0(杜绝「档位 0 → 时长爆炸」)、MIN_SPEED=0.01兜底防除零;区间夹取仍留在配置层,动效层不再夹一次(避免掩盖配置错误)。行为零变化(仅非整数档位下与旧round差 ≤1ms,测试与肉眼均不可见),全量 pytest 1511 → 1553 passed / 3 skipped(新增tests/test_motion.py42 项:token 键完整性与取值序、缩放口径与verify_nav_drag硬断言 110/220/440 严格对齐、八类脏档位回退、非正时长归零、缓动名必须是 Qt 真实成员名、AST 断言模块零 Qt 依赖且仅用标准库、三处接线点回归钉子)。顺带修掉三处已过期的口径注释(constants.py/tasks_panel模块 docstring /main_window.anim_speed属性)——它们是 A2 图标与 A3 空态的前置地基,reduce_motion开关未来只需改motion.py一处 - 键盘焦点态统一(UI 强化方案 A4 / 阶段 2,用户可见变更):此前全应用键盘 Tab 走查基本无反馈——输入框只有
$primary细边、按钮/列表/复选框对焦后毫无视觉变化,键盘用户在「现在焦点在哪」这件事上是瞎的。本轮补齐全应用焦点可见性:①新增主题 tokenfocus_ring(浅色#27787A/ 深色#6FFFE9)——刻意不复用$primary,因为实测浅色主题下主色对面板底对比度不足 3:1(WCAG 非文本 3:1 是硬线),focus_ring与$secondary_text同值同因(该类文字色本就按「浅底上要看得清」选过),并把它钉成test_theme_contrast.py里可执行的证据。②主窗 QSS 输入类(QLineEdit/QPlainTextEdit/stepValue):focus环色由$primary改$focus_ring并补outline: none;末尾追加统一焦点块覆盖按钮四族(#primaryBtn用$on_primary、#secondaryBtn/#iconBtn/#navBtn/#settingsNavBtn/#tableOpenBtn用$focus_ring、#dangerBtn用$danger)+QListWidget+ 复选框。③卡片窗 QSS 同步追加焦点块(sideTabIconBtn/modeBtn/navSiteCard/fragCopyBtn/appLaunchBtn→$focus_ring;modeBtn:checked:focus/nextBtn/taskAddBtn→$on_primary;cardCloseBtn/fragDelBtn→$danger;undoUndoBtn→$primary_lite;taskDate/taskList→$focus_ring)。④盒模型不变性铁律:焦点态只允许改border-color,不得动 padding/margin/尺寸——否则对焦瞬间触发重排,破坏大量既有几何断言;两处原本border: none的实底按钮(#nextBtn9px 24px /#taskAddBtn7px 15px)改为1px solid transparent时同步把 padding 各减 1px 补偿(→ 8px 23px / 6px 14px),盒尺寸逐像素不变。⑤ToggleSwitch自带focusInEvent/focusOutEvent+paintEvent内缩焦点环(控件固定 44×24,环必须画在内部,不能外扩占位)。★顺带挖出一个真 bug:QCheckBox:focus::indicator被 Qt 无条件应用——四态探针实测「从未聚焦」的复选框也恒有环,等于把焦点态变成了常态(写法看着像「子控件对焦态」,Qt 实际解析成「对焦后【及其子控件】」)。同测变体对照(A 未聚焦 / B 聚焦中 的环像素):QCheckBox:focus::indicator210/210(无条件,坏)、QCheckBox::indicator:focus0/128(条件性 OK)、QCheckBox:focus {color}0/20、QCheckBox:focus {border}0/57 → 定稿改用QCheckBox::indicator:focus,并加护栏test_no_unconditional_subcontrol_focus_form禁止该写法复发。回归钉子v4/tests/test_keyboard_focus.py(34 项:焦点规则禁 padding/margin/尺寸、边框宽恒 1px、非 indicator 规则必须有outline: none、覆盖清单齐备、NoFocus 控件(stepBtn/navGroupHeader)不得有死规则、对比度 ≥3:1、「为什么不用$primary」的可执行证据、实底按钮 padding 补偿、离屏渲染三例——secondaryBtn只扫顶边 6px 带(其文字色本就是$secondary_text,与$focus_ring同值,整块扫会假阳性)、QCheckBox两态都断言(「未聚焦必须无环」+「聚焦有环」,只断言后者是恒真陷阱)、ToggleSwitch焦点环)+ 离屏走查v4/tools/verify_focus_ring.py(56/56:真实 MainWindow 焦点环 + Tab 走查 + 控件台,主窗/卡片窗两套 QSS,light/dark 双主题真截图build/shots/focus-*.png)。顺带修掉test_theme_contrast.py一处随本轮暴露的脆弱点:它用整份 QSS 当parametrizeid,而本轮焦点块把 QSS 推过 Windows 环境变量 32767 字符上限(pytest 会把完整 nodeid 写进PYTEST_CURRENT_TEST),测试 body 全过、teardown 才炸、表象极易误判成 4 个测试失败 → 改用显式短 id("light-main_window"等)。全量 pytest 1553 → 1581 passed / 3 skipped / 0 error - 自绘图标体系(UI 强化方案 A2 / 阶段 3,用户可见变更):侧栏与页面标题此前全靠 emoji 字符当图标,而 emoji 是字体依赖的:实测 msyh(微软雅黑)下
▶ ▾ ▸ ✂ ⚙等符号QRawFont.supportsCharacter直接为 False、离屏环境更是QFontDatabase.families() == 0,渲染出来是一排 □ 豆腐块(阶段 2 的双主题截图里菜单/按钮图标肉眼可见仍是方块,正是本项的需求佐证);更麻烦的是打包发到用户机上,同一个 emoji 在不同系统/字体版本下外观能差出十万八千里。现改为纯自绘矢量图标:①技术路线是被打包现实逼出来的——QtSvg不可用(打包产物_internal/PyQt6/Qt6/bin/实测没有Qt6Svg.dll,为几个图标引入新运行时依赖不划算),而QPainterPath不解析 SVG pathd字符串(Qt 没有fromString),解析 SVG 路径本来也得靠 QtSvg → 于是自带一个 d 子集解析器,零依赖、零打包风险。②**src/icons.py(纯数据层,零 Qt 依赖):11 个图标路径(fragments三层叠片 /tasks圆角方框+对勾 /notes文档+三行字 /knowledge摊开书 /assets相框+太阳+山 /nav地球 /apps2×2 宫格 /plugins插头 /settings三根滑杆 /help圆圈+问号 /plugin包裹——插件页占位),几何助手circle/rrect/poly/dot(圆与圆角矩形一律用三次贝塞尔逼近,_BEZIER_K = 0.5522847498307936;dot用零长度线段 + RoundCap 得实心圆点),全模块只用re(AST 断言钉死)。解析器支持M/L/H/V/C/Q/Z大小写、M后的裸坐标组按L、科学计数法(1e-5里的e不算命令字母);刻意不支持S/T/A——S/T只是书写便利(可展开成C/Q),A的端点参数化→圆心参数化换算冗长易错,而圆和圆角矩形我们本来就是自己生成的。最阴的失败模式是「静默跳过」:不认识的字母被忽略会让图标悄悄画成完全另一个形状,所以不支持字母 / 非法字符 / 参数不足 / 命令前裸数字 / 结尾半组一律ValueError,绝不猜。③src/icon_render.py(Qt 渲染层):icon_path/icon_pixmap/icon/paint_icon/current_dpr/clear_cache;刻意用QGuiApplication而非QApplication(只依赖 QtGui,不把 QtWidgets 拖进依赖);QPixmap缓存键必须含(name, size, color, dpr, width)—— 少任何一维都会在换主题/换缩放时拿到脏位图,_color_key统一把#fff/QColor各种写法归一到HexArgb再入键;高分屏按width × dpr画进size × dpr位图后setDevicePixelRatio。④controls.IconLabel/controls.PageTitle:PageTitle唯一的设计约束是文字仍是QLabel#pageTitle——图标是独立子控件、不参与文字度量**,因此theme.py里QLabel#pageTitle的 QSS 与全项目既有的几何/样式断言口径零变化(把标题做成「一个自绘整体」会同时改掉文字度量与 QSS 命中,是没必要的风险)。换主题订阅宿主theme_changed,且订阅的是PageTitle自身的绑定方法而不是 lambda——绑定方法让 Qt 在部件析构时自动断开;lambda 没有接收者上下文,宿主活得比面板久时会在已析构对象上回调(段错误的经典成因)。⑤接线(P0 范围:侧栏 10 个内置页 + 各面板页标题 + 插件页占位,共 11 个位点):主窗侧栏按钮新增iconName属性 +_apply_button_icon/_apply_nav_icons,QIcon.State.On承载选中态 → 选中项图标自动变主色、零信号接线(离屏实测QIcon.State.On对 checkableQPushButton生效);NAV_PAGE_TITLES去 emoji 并新增NAV_PAGE_TITLES_FIXED(设置/说明是固定非拖拽项);register_plugin_page开头strip_leading_emoji(title)—— 插件 manifest 里带的 emoji 一律剥离(整串都是图标时原样保留,避免把纯图标标题清空),图标由宿主统一给;10 处面板页标题(碎片/日程/笔记/知识库/素材/网址导航/插件中心/软件导航/设置/使用说明)统一换PageTitle。⑥顺带修掉一个真 bug:PageTitle订阅宿主信号时只判is not None不够——全量 pytest 冒出 3 个 error(test_ui_scale.py:'types.SimpleNamespace' object has no attribute 'connect'),因为该文件的宿主替身是SimpleNamespace(theme_changed=SimpleNamespace(emit=...)):属性在、connect不在。改判callable(getattr(signal, "connect", None))(连connect=None的形态也一并挡住),与settings_panel._on_ui_scale_changed既有的callable守卫同口径。⑦★本轮最值钱的一条经验是测试脚手架硬崩:pytest tests/test_icons.py收集 62 项、打完 51 个点、在第一个创建 QApplication 的测试上静默死掉——没有 traceback、没有失败摘要、退出码 127,而前 51 项全是纯逻辑不碰 Qt,崩点恰好落在「第一个 Qt 测试」上,看上去非常像渲染层的问题。逐语句无缓冲打点 + 减法二分后坐实:_app()辅助函数写成return QApplication.instance() or QApplication([]),而调用点裸调用、丢弃返回值 —— PyQt6 下QApplication([])的 Python 包装对象持有 C++ 实例的所有权,函数一返回引用计数归零 → C++ 侧 QApplication 被销毁 → 紧接着QPixmap构造触发 Qt 的qFatal("QPixmap: Must construct a QGuiApplication before a QPixmap")→ 进程 abort,连 pytest 摘要都来不及打。对照组test_keyboard_focus.py用同一个 helper 却一直没事,差别只在它写的是app = _app()(引用留在调用方)——这也是为什么第一轮排查方向(「QApplication 获取方式不一致」)反而对了一半:真正不一致的是引用有没有被接住。修法:helper 内部把 app 存进模块级_APP再返回。PyQt6 里 QApplication / QPixmap 这类带所有权的对象,绝不能在辅助函数里返回后就被丢弃。回归钉子tests/test_icons.py(63 项:数据健全性 6——非空 str dict / 名称唯一 / 路径可解析且命令在M/L/H/V/C/Q/Z内 / 坐标全部落在 0..24 视框内 /_ARGC键全集;解析器健壮性——5 类 malformed + 6 个不支持字母 + 非法字符 + 科学计数法可接受 + 相对/隐式命令 +Q/Z;几何助手 5 项;零 Qt 依赖 AST 双断言(顶层 import 必须 =={"re"},且源码里不得出现任何 PyQt6 import/符号——初版用字符串搜索会命中模块头注释里反复出现的 "PyQt6" 字样,与test_kb_search_core.py已记录的同类陷阱一模一样);前缀剥离 20 项(12 个带 ASCII id 的 parametrize + 纯图标不剥 + 非 str 透传 + 中间 emoji 不剥);覆盖清单 4 项(NAV_ICON与NAV_FIXED_ITEM_GROUP ∪ _BASE_GROUP_KEYS一致、三张表与NAV_ICON键一致);渲染层 Qt 6 项;控件接线 6 项)+ 离屏端到端tools/verify_icons_render.py(34/34:A 11 图标 × 4 档尺寸(16/24/32/48)不透明像素数全 > 0(实测 82..170 / 136..320 / 190..420 / 272..640);B 外接框占比 ≥ 25%(防「画是画了但缩成一个小点」);C 双主题 × 两个 token(text/text_secondary)× 11 图标 渲出的像素色 == 传入色;D 缓存语义 5 项(同参同图 / 异色不共用 / 异尺寸不共用 / 异写法归一 /clear_cache生效);E 真窗口 ×2 主题 8 项(全导航按钮图标已登记 / 文案无 emoji 残留 / 插件页剥离 + 占位图标 / Off 态 ==text_secondary/ On 态 ==primary/ 用两个不同按钮扫QRectF(20,0,16,h)图标带取证);G 换主题跟随(判据是「均值色离哪个参考色更近」——两主题的text_secondary只差 ~13,用数像素或宽容差都会假阳性);H 面板标题 4 项(10 个页面各恰 1 个PageTitle+ 图标名正确 +objectName()=="pageTitle"/ 全窗 ≥10 / 取色 == 主题text/ 透明底渲染笔画均值色更接近对应主题text),附 light/dark 双主题真截图build/shots/icons-*.png)。验证方法论两处坑记档:①QWidget.grab()会用调色板窗口底色把整张位图填满,底噪直接把笔画色淹掉(H3 一度得到(177,183,188)既不像浅色也不像深色的荒谬均值)→ 新增render_transparent(),走render(pm, QPoint, QRegion(), QWidget.RenderFlag.DrawChildren)+ 透明底,并给IconLabel加color属性做直接断言;②QRegion在 QtGui 不在 QtCore(QPoint才在 QtCore);③test_strip_leading_emoji的parametrize必须给 ASCIIids=[...],否则非 ASCII id 会在 GBK 控制台把 pytest 输出打断。全量 pytest 1581 → 1650 passed / 3 skipped / 6 subtests passed / 0 error;离屏回归全绿——verify_focus_ring56/56、verify_nav_groups、verify_nav_live_letway9/9、verify_plugins_page19/19、verify_plugins_panel23/23、verify_theme_live_switch32/32、verify_app_context_menu10/10、verify_recurring_tasks46/46、verify_text_workshop34/34、verify_plugin_store40/40、verify_kb_search55/55、verify_card_nav_grid、verify_settings_groups8 项 - 动作按钮图标化(UI 强化方案 A2 / P1,用户可见变更):界面残留的最后一批 emoji 按钮字形全部换成自绘矢量图标——此前标题栏的 🌙☀️ ⛶ ❐ ×、便签 ✕、以及各工具栏「🔄 刷新 / 🗑 删除 / ✏️ 编辑 / 💾 保存 / 🔍 检查更新」的 emoji 前缀,在离屏环境、精简系统、缺字形机器上渲染成 □ 豆腐块,且外观随系统字体漂移。①**
icons.py新增 14 个动作图标**(pin图钉 /trash垃圾桶 /refresh弧线+三角箭头 /edit铅笔 /save软盘 /search放大镜 /close/minimize/maximize/restore/moon/sun/plus/minus)与arc()助手((4/3)·tan(Δ/4)·r控制柄是_BEZIER_K在任意圆心角上的一般化;月牙=外圆长弧+裁剪圆短弧的两圆求交数值解);顶层 import 放行math(AST 守卫同步改为{re, math},仍零 Qt 依赖)。②**controls.IconButton:纯图标(固定方尺寸)与「图标+文字」两用;★各站点 objectName 原样保留**(#iconBtn/#dangerBtn/#stepBtn/#sideTabIconBtn/#secondaryBtn/#cardCloseBtn等 QSS 契约不破,本类只负责 QSScolor:管不到的 QIcon 位图);常态/悬停/选中/禁用四态取色(缺省text_secondary/primary/primary/text_disabled,可传 token 名或#RRGGBB字面量;主色底按钮传on_primary让图标跟文字同色);悬停换色走 enterEvent/leaveEvent 重设 Off 态位图(pixmap 缓存按颜色分键,进出各只付一次生成成本);主题跟随三通道=宿主theme_changed订阅(PageTitle 同款callable守卫)+ 父链_theme探测兜底(插件页运行期新建的按钮不用接宿主管线也能立即取对色)+main_window._apply_theme全窗findChildren(IconButton)fan-out(卡片窗/玻璃弹窗/便签各自在自家 apply_theme 里循环)。③位点替换 43 处:标题栏 4 钮(🌙/☀️、⛶/❐ 一钮两态由setText改set_icon_name)、玻璃弹窗/小卡片/便签关闭钮、小卡片侧 Tab ×7(复用现成导航图标,_TABS的 emoji 仅留作注释)、Stepper ±(原先挑 U+2212/ASCII+ 的字体权衡作废,自绘后与字体无关)、AI 助手规则行 🗑/+添加规则/💾保存规则、kb-search 🔄重建索引、周报 💾另存为;工具栏/页脚混排按钮 ~26 处剥 emoji 前缀 + setIcon(碎片 ×3 / 素材 ×2 / 设置 ×3 / 任务 ×2 / 笔记 ×2 / 知识库 ×5 / 软件导航 ×3 / 插件中心 ×3 / 其余页脚;GlassDialog.add_footer新增可选第 4/5 元素=图标名+常态色,老调用点零变化)。④刻意不做:右键菜单/托盘菜单/确认弹窗的 emoji 文案(真实系统菜单字体渲染正常且文本断言密集),📋 复制 / 👁 预览 / 📥 加入碎片池等非本轮六类 emoji 留待后续批次。⑤验证:全量 pytest 1650 → 1660 passed(test_icons.py+10:P1 图标登记契约 / objectName 与方尺寸透传 / 三态位图 / 悬停换位图并还原 /set_icon_name换形 / 主题订阅与 SimpleNamespace 替身容忍 / 父链探测含脏值防误判 /icon()Disabled 位图);verify_icons_render.py34 → 44/44(B 段对一维字形 minimize/minus 改按「跨度≥50%」判——面积判据对线条字形恒假;新增 H 段「真窗口动作按钮」:数量≥18 / 三态位图齐全 / 文案无 emoji 残留 / 一钮两态 / Stepper 契约保持 / 换主题取色跟随 + 双主题整窗截图build/shots/icons-actions-*.png);其余离屏回归全绿(focus_ring 56/56、theme_live_switch 32/32、card_nav_grid、card_close 9/9、settings_groups 8 项、plugins_panel 23/23、kb_search 55/55、sticky_notes 22/22、card_click_feedback 6/6);受影响断言面为零——「⬇ 安装」「✂ 截图钉屏」等 emoji 文本断言全部不在迁移范围(两个本已过期的 verify 脚本_ai_assistant/_weekly_report走独立对齐任务,前者一条=="保存并测试连接"反而因本次剥 emoji 开始转绿) - 空状态通用化 + 补齐 7 页(UI 强化方案 A3 / 阶段 4,用户可见变更):此前只有碎片页是结构化空态(图标+标题+提示+动作钮),笔记/素材/网址导航/插件中心是一行干巴巴的占位文字,任务/知识库/软件导航三页干脆什么都没有(只有"共 0"计数,空白区毫无引导)。现把碎片页的
_EmptyState提升为controls.EmptyState(自绘图标 + sectionLabel 标题 + hintLabel 提示 + 可选动作钮),8 个页面全部结构化:①fragments 改引用组件(set_mode两态改set_state,_action点击语义与 QSS 契约逐字保留,emoji 图标 🧩/🔍 换自绘 fragments/search);②notes:QLabel 占位 → EmptyState 覆盖层,搜索无命中给「没有匹配「关键词」的笔记」态;③assets:_stack空态页换成 EmptyState;④nav:_NavList占位 QLabel → EmptyState(空态高度 72→150);⑤plugins:外层空态换 EmptyState(objectName 另起pluginEmptyState——QSS 的QLabel#pluginEmptyHint是商店弹窗的契约不能动,verify_kb_search 断言其存在);⑥tasks / knowledge / 软件导航三页从无到有:任务页「还没有任务:在上方输入待办回车添加,到期会在悬浮球提醒你」、知识库「知识库还是空的:点上方「新增知识」…」(只有整库为空才显示,搜索无命中仍走列表内占位条目——两种"没有"语义不同不混用)、软件导航「还没有常用软件:点上方「新增」…」。实现要点:attach_to覆盖层模式——EmptyState 叠成列表控件的子件、随 resize 贴合(自带 eventFilter),列表本体一行不动,itemAt(0)/count()定值断言与动态重建(软件导航_clear_layout会清空整张网格)全部避开;无动作钮的空态WA_TransparentForMouseEvents鼠标全穿透(右键/滚轮照常落到列表);图标取色走主题text_placeholder并在宿主 apply_theme 链里跟随。★护栏已知雷全部绕开(先侦察后动手):plugins 卡片网格有精确占位断言(EmptyState 绝不进_cards_layout)、每页恰好一个PageTitle的 H 段断言(EmptyState 内禁止 pageTitle 字样)、计数标签口径不动。回归:tests/test_empty_state.py9 项新增(构造契约/QSS objectName 老契约/动作接线/无 pageTitle 护栏/图标登记/两态切换/主题取色/attach_to 几何/鼠标穿透)+ 新增离屏端到端tools/verify_empty_state.py(16/16:真 MainWindow 空数据走查 7 页 ×2 主题空态可见 + plugins 页用 loader=None 最小宿主单独渲染,16 张双主题截图build/shots/empty-*.png);受影响 verify 全绿(fragment_category 14/14、plugins_panel 23/23、kb_search 55/55、list_perf 12/12、md_export 34/34、icons_render 44/44);全量 pytest 1660 → 1669 passed;★记档:隐藏的顶层控件 resize 事件在 show 前不发(attach_to 的测试须先 show 宿主再 resize);页面索引用NAV_PAGE_INDEX查表(栈里 6=设置、9=plugins,写死数字必踩坑) - 减弱动效开关(#14 / UI 强化方案收尾,用户可见变更):设置 → 外观与主题新增「减弱动效」开关——打开后导航展开/掉落、任务勾选、卡片窗口的所有过渡动画一律瞬显,适合低性能机器与晕动效用户(悬浮球自身的动画不在此口径内,仍按「动画速度」倍速走)。实现是 A1 地基的红利兑现:动效时长早已收敛进
motion.duration()单点,本轮只加了一个模块级总闸——motion.set_reduce_motion(),开启时duration()(含eased_ms、含脏输入)一律返回 0;语义刻意选「瞬显」而不是「按比例缩短」——部分缩短会让动画停在中间帧,观感更差。配置三件套同步(reduce_motion默认 False;bool 不进_CONFIG_RANGES——区间夹取只属数值档位,既定口径);主窗启动即按配置置位,设置页 ToggleSwitch 翻转即时持久化 + 总闸同步,「恢复默认设置」路径同步回读。回归:test_motion.py+6(默认关闭口径不变 / 开启后全输入归零 / 开关可逆 / config 三件套与 bool 归一 / 不在_CONFIG_RANGES/ 设置页行端到端翻转落盘并同步总闸),全量 pytest 1669 → 1675 passed;verify_settings_groups8 项(卡片数不变)、verify_nav_live_letway9/9、verify_nav_groups全过(motion 消费方零回归) - 侧栏分组展开/折叠丝滑度调优(用户可见变更):侧栏分组收放此前有三个可感瑕疵,全部经离屏探针取证后修掉——①连点重定向跳变:快速连点同一组头时,旧实现先
_stop_nav_group_anims()落终态再按新目标重放,折叠中途再展开会先「啪」地弹回满高再收(探针 C:中断后首拍 39px 满高、~13ms 后才回落);②stagger 延迟期满高僵住:逐条错峰期间尚未起播的条目以未约束满高亮出(探针 B:末条 0.9ms 即达 39px,假「瞬间到位」,last_movement 307ms);③急起—长尾漂移:OutQuint 对 39px 行高 ~105ms 就走完 97%,余下 175ms 只挪 1px。修复三件套:重定向代替打断——新增_stop_nav_group_anims_keep_pose(group)只停本组在飞动画且不落终态(动画对象以动态属性_nav_group标记组归属,别组动画不受影响;拖拽路径仍走落终态的_stop_nav_group_anims()——槽位几何正确性依赖它),条目起播值改取当前视觉高度(隐藏=0 / 未约束=行高 / 在飞=当前 maximumHeight),连续点击从半途丝滑续走;先压高再显形——setMaximumHeight(start_v)必须先于setVisible(True),stagger 延迟期与重定向首帧都以起点姿态亮出(根因:QSequentialAnimationGroup 的 addPause 期间条目保持未约束态);缓动/时长收紧——OutQuint→OutCubic,280/210/18→220/170/12(motion.MOTION全局档位数值不动,注释解耦:侧栏本地常量只服务侧栏)。效果:末条 reach 0.9→178.7ms、last_movement 307→243ms、中断后满高帧 True→False(首拍 29px 半途姿态续走)。回归:护栏全绿verify_nav_groups49 项(E/F 中间高度采样证明过渡真实、H 段拖拽落终态契约未破坏)/verify_nav_live_letway9/9 + 全量 pytest 1687 passed / 3 skipped / 6 subtests 精确对账基线 + test_init 冒烟全 OK + 双主题真截图(light/dark 稳定态 + 中间帧半高过渡,build/shots/nav_smooth_*.png) - 便签浮窗玻璃化(用户可见变更):桌面便签此前是「teal 实底标题栏 + 直角实底窗」的早期朴素画风,与全应用玻璃拟态割裂。现对齐主窗语言:
WA_TranslucentBackground+paintEvent手绘圆角面板(RADIUS=10,glass_fill填充 +primary_border描边),标题栏/正文透明融入本体、仅以 1pxprimary_border分隔线分层,标题文字/关闭钮/缩放抓手全部改走主题 token(text/text_secondary,关闭钮悬停danger)——light 白玻璃、dark 深蓝黑玻璃,随主题广播实时换肤;交互契约零改动(26px 标题热区、右下抓手、600ms 几何防抖、透明度三档)。两处非显而易见的实现决策:①本体刻意手绘而非窗口 QSS background——translucent 顶层窗的本体 QSS 背景在渲染管线里不可靠(offscreen grab 实测整窗透明、连WA_StyledBackground都救不回,被 E1 像素护栏当场抓住),CardWindow 为画阴影走 paintEvent 是本项目先例;②QColor 不认函数式 rgba() 串——theme token 的glass_fill="rgba(255,255,255,224)"(alpha 0-255)与primary_border="rgba(91,192,190,0.30)"(alpha 比例)只能被 QSS 解析器消费,本体手绘走_css_color小解析器(3~4 个数值分量,第 4 参 >1 按 0-255、≤1 按比例,解析失败回退兜底色绝不画黑)。回归:verify_sticky_notes22/22(E1 取样同步改为整窗 grab——标题栏透明后抓本体只会得到透明底、两主题无差恒假);全量 pytest 1743 passed / 3 skipped(并行会话同日新增 67 项一并全绿);双主题真截图build/shots/sticky_{note,task}_{light,dark}.png - 窗口材质(UI 阶跃 J1,已代码级回退):【2026-10-01 实机回退】深色 Mica 实机观感发闷(用户看截图拍板),代码已全部移除——
src/dwm_material.py、theme.material_colors()、get_main_window_qss(material=)参数、主窗全部接线(_material_wanted/_apply_backdrop/apply_window_material/showEvent 挂载/_apply_theme分流)、设置行「窗口材质」、config 键window_material、tests/test_dwm_material.py、tools/demo_backdrop.py全部删除,主窗观感与 09-30 收官版逐字节一致(今早的图标体系 P1 与 #12/#13/#14 完好未动)。以下为存档技术结论(未来重试材质时的现成地基,备份见.workbuddy/backups/1240_material/):期 0 demo 实测 Qt6 的WA_TranslucentBackground窗口不是 WS_EX_LAYERED(EXSTYLE=0x100)→ DWM backdrop 与现有无边框+半透明体系不互斥,真主窗可挂 Mica 且浅→深主题实时切换保持;两处必踩坑已验证——初始化期提前取 winId 会抢在半透明属性之前建原生窗(须WA_WState_Created守卫)、import verify_icons_render会把QT_QPA_PLATFORM打回 offscreen 致真桌面预览从未上屏。回归:全量 pytest 1675 passed(材质 11 项测试随代码一同移除,数目精确对账)Win11 22H2(build 22621+)上经DWMWA_SYSTEMBACKDROP_TYPE给主窗挂系统级 Mica 背景模糊,同时主窗玻璃填充切换到低实度变体(light:glass_fill 88%→63%、panel_fill 67%→54%;dark:82%→59%)——此前"玻璃"实为挡壁纸所迫的高实度磨砂(theme.py旧注释「67% 背景会打架」),真模糊交给系统合成器后自绘填充降为薄纱;关闭 = 经典配方逐字节不变;低于 22621 / offscreen / 任何 DWM 失败一律静默回经典(回滚 = 关开关)。实现要点:①期 0 demo(tools/demo_backdrop.py,独立脚本不合入产品链路)实测回答方案 6 问的关键三条:Qt6 的WA_TranslucentBackground窗口不是 WS_EX_LAYERED(EXSTYLE=0x100)→ backdrop 与现有无边框+半透明+自绘软阴影体系不互斥;属性在真主窗读回 Mica 且浅→深主题实时切换保持;②src/dwm_material.py纯 ctypes 零 Qt(能力门控 / 取值编码 / 注入 setter 全部离线可测,AST 禁 PyQt6——打包 DLL 清单零新增);③theme.material_colors()纯函数变体只覆盖 glass_fill/panel_fill、无全局态——卡片窗/便签/玻璃弹窗永远经典配方(get_card_window_qss无 material 参数作防污染钉子);get_main_window_qss(material=)老调用点默认输出逐字节不变;④主窗_apply_backdrop挂WA_WState_Created守卫——初始化期提前取 winId 会抢在WA_TranslucentBackground之前建原生窗毁掉半透明(预览脚本实踩);backdrop 暗色位跟主题实时切换;⑤验证方法论:import verify_icons_render会经模块顶部setdefault把QT_QPA_PLATFORM打回 offscreen——真桌面预览必须在建 QApplication 前显式覆盖回windows,否则窗口从未上屏、读回恒 0。回归:tests/test_dwm_material.py11 项,全量 pytest 1675 → 1686 passed;填充百分比是首档手感,实机不满意直接调theme.material_colors里的四个 rgba - 悬浮球悬停轮询空闲降频(3.6):
HoverPollPolicy状态机——鼠标静止 1s 后轮询 50ms→200ms,任何活动立即恢复 50ms;吸边隐藏/拖拽逻辑零改动(代价:静止后快速甩离的收卡判定最坏 ~400ms,原 ~100ms,可接受) - 巨型入口文件第一批拆分(4.3 部分):托盘创建/右键菜单/气泡整体抽到
src/tray.py(TrayController,依赖显式注入),快捕条/截图/插件三组全局热键注册样板收敛为src/hotkey_binding.py绑定表驱动;knowledge_ball.py 3613→3558 行,test_host_attribute_contract扩展 145 行钉住新公开 API;wiring.py 大拆与剩余私有穿透点留待下轮(/命令面板开工前必须完成) - 全局 Esc 不再退出程序(成熟化批次 1.3):删除「任何窗口按 Esc 都杀进程」的全局快捷键(与桌面软件惯例相反、最易误触且后果最重);Esc 逐表面收口——卡片/便签关闭、快捕条收起、截图框选取消(后两者本就支持),主窗口 Esc 无动作;退出只走托盘菜单/悬浮球右键/主窗菜单三处
- 退出显式收尾(1.4):
aboutToQuit与_safe_quit统一收口到幂等的_shutdown_once——注销全部 3 组全局热键(兑现global_hotkey.py自家注释的约定)、主动AI_SERVER.stop()(本地 llama-server 不再只靠 JobObject 内核兜底)、停剪贴板监听;单步失败只告警不阻断退出 - 发布包默认内置空白知识库模板(用户可见变更):主程序 zip 此前不带
float_data/知识库.docx(防个人内容夹带),但下载用户首次打开知识库页是「共 0 段」且写不进去(新增段落 / 碎片加入知识库都因文件不存在而失败,点「🔄 重新加载」还会弹「找不到文件」),知识库功能等于摆设。现tools/build_release.py在不带--with-knowledge时现场生成一份空白模板补进包:纯标准库手造 OOXML 最小三件套([Content_Types].xml+_rels/.rels+word/document.xml),内含一段可编辑可删除的欢迎示例,实测 python-docx 对它「打开 / add_paragraph / save」全通过(Word / WPS 亦可正常打开),不依赖 python-docx、CI 零负担;--with-knowledge(打真实文件)时行为不变。事后校验新增断言「包内必须有float_data/知识库.docx」,真实文件与模板都没有就删输出报错、不留坏包。隐私口径不变:模板内容是写死在脚本里的欢迎语,绝不读本机真实知识库
Changed
- 临时素材加载丝滑化(异步缩略图管线,用户可见变更):素材页此前在
paint()里同步解码缩略图——首次打开/滚动到新行时,QImageReader 在 UI 线程逐张解码,实测单张 2400×1600 噪声 PNG 约 43ms、12 张混合素材合计约 161ms(= 首帧冻结时长,多张大图时肉眼可见卡顿)。现改为占位即显 + 后台解码 + 淡入三段式:①paint()未命中缓存只画类型占位图并落_PENDING哨兵(绘制路径永不解码),由面板的QThreadPool(2 线程,不抢满核)后台做「解码期缩放 → Smooth → 居中裁切」(与旧产物逐像素同规格),经 Queued 信号回主线程QPixmap.fromImage入缓存;②到货 140ms OutCubic 淡入(时长走motion.duration总闸,reduce_motion 开=瞬显),并只重绘该格(visualItemRect局部 update,不惊动整页);③代际防串:apply_thumb_size变尺寸时_epoch+1,旧尺寸的在途结果按代作废,不会出现旧图回填;在途去重(_pending集合)、素材删除后的迟到结果按_valid_ids丢弃、读失败仍落False哨兵防反复重读,缓存清理口径与旧版一致。④非图片/文件失效仍走原占位分支;loader=None的独立使用场景保持旧同步路径(兼容直用 delegate 的既有脚本)。度量:打开素材页列表即刻就绪(12 条目无解码阻塞),加载期 400ms 内事件循环跑 152 拍(主线程持续响应),5s 内全部到货;变尺寸 96px 后按新宽度全部重灌。回归:新增tests/test_asset_thumbs.py12 项(解码纯函数 / 占位+派发恰一次 / 非图片哨兵 / 同步回退 / 到货入缓存 / 换代丢弃旧结果 / 删除素材迟到结果丢弃 / 缓存与淡出剪枝 / 同值短路 / 淡入完成摘除 / reduce_motion 瞬显);新增离屏端到端tools/verify_asset_loading.py(6/6:真窗大图全链路);verify_asset_thumb_setting.py两处同步假设改等待式(小图 150ms 内实际也能到货,加固防慢机抖动)并 5/5 通过;全量 pytest 1675 → 1687 passed
Changed
- 插件中心观感优化(纯视觉,功能与交互零变化):针对卡片"信息全同级灰、像一堵文字墙"的三层重排——①层次:插件描述从次要灰升为主文字色(它才是用户要读的内容),依赖/能力/id 元信息行降为 11px 次级灰,使用说明摘要同步降级——标题亮、描述可读、元信息退后的三档 hierarchy;②降噪:
依赖: PyQt6.QtCore…这类开发者信息不再独占一行,并入能力行的悬停 tooltip(仅当插件只声明依赖、无能力时才保留独立行,信息不丢);③破坏性操作分级:「卸载」从普通次按钮改为危险色(#dangerBtnQSS + 图标同色,悬停实底红白字),与「停用/打开目录/查看使用说明」拉开视觉权重;④状态徽章(已启用/已停用/包不合法)加粗 + 加大内距圆角,更"徽章";⑤卡片内距与行距各 +1~2px 呼吸感。所有 objectName 契约(pluginCard/卡片网格精确占位断言/各文案断言)逐字未动。回归:全量 pytest 1687 passed 不变;插件族 verify 全绿(plugins_panel 23/23、plugins_page 19/19、plugin_store 40/40、plugin_p0p1、plugin_market 10/10);深浅双主题截图build/shots/plugins-polish-{dark,light}.png
Fixed
- 两个过期 verify 脚本对齐现行架构(只改脚本、不改产品代码):①
tools/verify_ai_assistant.py此前停在第 15 项就硬崩(B4 起连环红)——2026-09-30「AI 后端收归设置页总配置」重构后,脚本仍按 09-28 的页面级后端口径断言:build_request用旧键backend_mode/cloud_*(新键为mode/base_url/api_key/model/local_*)、引用已删除的页面设置卡与本地服务状态机(_settings_card/LOCAL_SERVER/_mode_*_btn/_switch_mode/_cloud_active闸门)。对齐后 114→121/121 全绿:假 ctx 注入ai_providers(params/add_listener/remove_listener/stop_local/is_attached)模拟设置页;新增「构造即订阅 / 销毁即退订 / ready 广播→快捷停止钮出现 / 停止走 ctx.ai.stop_local / 参数实时直读(改快照下一发即生效)/ 本地未就绪拦截」六项跟随断言替代旧模式切换断言;B7b 改判「旧后端键被白名单合并忽略(零残留)」;C19 改判「插件配置零后端键」;C23-C25/C31(断开闸门、_collect_settings)随功能移除一并删除(设置页侧由 verify_ai_backend.py 覆盖);★踩坑记档:_clear_chat的deleteLater是延迟删除,布局项即时消失但行容器 QWidget 仍挂在_stream_host下,findChildren会数到"幽灵卡"——清场后须显式sendPostedEvents(None, DeferredDelete)再跑按 findChildren 计数的断言(C20 计数污染、C28 的 120px 下限按新首卡「对话已清空。」改 80px 防塌缩地板)。②tools/verify_weekly_report.py两处:A3「热键注册表只允许 weekly-report 一个」改为包含判定(2026-10 起 5 个插件各注册热键);F6 另存为按钮文案随 P1 图标化剥除 emoji 前缀。回归:verify_ai_assistant 121/121、verify_weekly_report 65/65、全量 pytest 1660 passed 不变 - 离屏脚本路径自举统一(开发工具修复,5 个文件):验证脚本此前有两类路径硬编码,换机器 / 改目录名 / 换用户目录即整体失效。①
v4/tools/verify_card_nav_grid.py写死sys.path.insert(0, <项目根>/v3),而 v3 目录早已废弃删除 → 该脚本自 v4 独立后一直跑不起来(ModuleNotFoundError: No module named 'src');②另有 4 个文件把绝对路径写死成r"D:\桌面\AI Port\FloatPulse\v4"——tools/verify_card_click_feedback.py、tools/verify_plugins_panel.py、tools/verify_plugin_store.py、tests/test_plugin_market.py(后者还写死REPO_ROOT用于读plugin_store/kb-search.fpplug)。现全部改为按__file__推导(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),与同目录其余 15 个脚本同一写法),REPO_ROOT再多上溯一层。回归验证:verify_card_nav_grid.py5/5 段 PASS(域名提取 / 卡片统一 46×173 / 右边缘 351 ≤ 内容区 352 不溢出 / 双列 4 行×2 / 标题截断+域名副标题+tooltip)、verify_card_click_feedback.py6/6、verify_plugins_panel.py23/23、verify_plugin_store.py40/40、pytest tests/test_plugin_market.py27 passed、全量 pytest 1511 passed / 3 skipped;全仓再扫一遍已无D:\桌面类项目路径残留 - 未完成任务不弹托盘提醒(用户报障,2026-09-30 修复):设置里「任务提醒」是开的、也没有任何拦截,但启动与每日 9:00 两处通道都不出声。根因是提醒的判定口径漏了一整类任务——
_check_task_reminders只收STATE_TODAY(今日到期)与STATE_OVERDUE(已逾期),而用户手头 5 条未完成任务截止日全是空串(随手记下来的待办,还没来得及安排日期),task_state("")归为STATE_NONE→ 三处都不匹配 → 末尾if not due and not overdue: return静默返回。真实数据复算:修复前 0 项、修复后 5 项。修复把「未完成任务的提醒口径」下沉成纯函数task_manager.bucket_unfinished(tasks, today)(无 Qt 依赖,pytest 可直接跑),返回三桶(overdue, today_due, undated)——新增「未安排日期」桶,先跳过done=True,再按task_state分流;knowledge_ball._check_task_reminders改为按三桶拼气泡(每组标题 + 最多 5 条标题 + 「…另有 N 项」,6000ms 气泡,总数进标题),判定与渲染彻底分离,以后新增桶只改一处。刻意不动refresh_badge():徽标数字仍只算逾期 + 今日到期(徽标是「今天要看什么」的紧迫度信号,把「没日期的待办」也堆上去会让数字长期虚高、失去提示意义),气泡才是「你可以处理一下这些」的完整清单——两者口径有意不同且在设置页行描述里写明。文案同步四处(设置页行描述 / 帮助页「任务提醒」/config.py注释与默认值行注释)。回归钉子v4/tests/test_task_reminder.py(7 项:空 deadline 未完成必被收 / 六种脏值""·" "·None·"2026/09/30"·"2026-13-40"·"今天"全归未安排 / 三桶互斥且保序 / 已完成永不收 / 空输入 / 复刻用户场景 5 条全空 → 提醒数 = 5 /STATE_NONE常量接线) - 「小卡片保持显示」关着但卡片常驻不收回(用户报障,2026-09-30 修复):设置里该开关是关的(
config.json实测card_always_show=false,截图里也是灰的),悬浮球的小卡片却一直挂在屏幕上不自动收回——看起来像「设置没保存」,实际是拖动锁卡死。根因:CardWindow.is_locked()直接return self._dragging,而_dragging只在mouseReleaseEvent里清零;一旦这次 release 丢失(拖动中途切窗 / 弹模态 / 抬起点落到别处),标志位就永久为 True,而宿主的两条收回路径——悬停检测_check_hover_state与看门狗_card_watchdog_tick——双双以if card.is_locked(): return早退,连兜底的看门狗都救不回来,卡片就此滞留(用户感知为「开关失效」)。修复给拖动锁加物理按键交叉校验:is_locked()在_dragging为真时再问一次QApplication.mouseButtons() & LeftButton,左键实际已松开就就地自愈(清标志 + 补发card_drag_finished让宿主正常收尾)并返回 False;mouseMoveEvent同样补一道(收到不带左键的移动事件说明按键已抬)。正常拖动路径零变化(拖动中左键必然按着,判定恒过),只把「release 丢失」这一异常分支兜住。回归钉子v4/tools/verify_card_close.py(9/9 离屏端到端,make_ball(always_show)造真ConfigManager+ 隔离指针把「鼠标既不在球上也不在卡片上」钉死):A1/A2 关闭态自动收回、B1 常驻态保持、C1 运行中改开关即时收回、D1/D2/D3 僵尸拖动锁被is_locked()自愈 + 补发card_drag_finished、F1 自愈后卡片能正常收回、E1 基线恒 False;顺带修掉并行改动留下的过期替身(verify_card_click_feedback.py的FakeCardWindow补current_mode特性与switch_mode方法,6/6 恢复通过) - verify_settings_groups 适配分类导航版式:2026-09-29 设置页改「左导航 + 分类 QStackedWidget」后该脚本一直按旧单页世界写死(跨页 y 排序无意义、期望 8 卡实际 10 卡)——分组卡收集改为按栈页序展开,期望表对齐 9 分类卡 + 软件更新子卡,分隔线校验升级为逐卡动态(行数 - 1,以后新增分组/行不再使脚本过期);顺带清理上批重构遗留的调试探针文件
- 测试盲区补齐(4.4):clipboard_monitor 与 screenshot_pin 各补逻辑层回归(前者:去重/自写抑制/开关启停/文本-路径-文件分发/容量裁剪信号/应用过滤/图片捕获闸门,不碰真实剪贴板;后者:框选归一化与 Esc 取消、dpr 换算、光标锚定缩放与边界不抖动、抓手等比 resize 钳制、批注撤销栈像素级断言);顺带修掉框选真 bug——
QRect(p1,p2)右下角含端点语义使_selection()的归一化结果随拖拽方向差 1px(同两个角点反向拖得到不同裁剪),现取两角包围盒保证方向不变;另修正 2 处剪贴板测试预期与实现不一致(trim 信号按次通报、FragmentManager 自带 12 条窗口去重) - 换主题后碎片列表文字不跟随(用户报障,2026-09-30 修复):点右上角主题按钮换风格时,碎片工作台的列表文字停留在旧主题配色——浅色主题下仍是深色主题的字色(
#E4E8EE落在白底上几乎不可见),必须切一次页面才恢复。根因是列表有两处颜色不经 QSS:①绘制代理(_MatchHighlightDelegate)的字色 / 时间色 / 内容命中高亮色 / 类别色条;②条目前景色(日期分组行=主色、路径行=link 色)。两者都只在refresh()里赋值,而 refresh 仅由「切到该页」的host.refresh_page()触发,故换主题时列表仍是旧配色。修复:新增FragmentsPanel.apply_theme()——按主题 token 就地重取色 + 重绘,不重建列表(换主题与数据无关,重建会白跑一遍筛选/搜索并多发一次data_changed),由MainWindow._apply_theme()显式调用;同时把日期行/路径行颜色从写死的theme == "light" ? A : B三元表达式改为按主题词取色(新增COLOR_TOKEN_ROLE存 token,让取色点唯一),杜绝下次再出现「一个主题漏改」。顺带修掉同类残留:知识库页「⚠️ 检测到外部修改」的警告文字是 inline stylesheet 着色,QSS 换主题同样刷不到,现抽出_apply_modify_label_color()并由KnowledgePanel.apply_theme()在警告仍显示时重新着色。回归钉子v4/tools/verify_theme_live_switch.py(32/32,四段式):A 面板级逐项断言代理 / 条目 / 类别色条 / 高亮色双向跟随;B 真实 MainWindow 端到端——点右上角按钮换主题且不切页面,直接抓列表 viewport 数像素(旧主题字色像素 5335 → 0、新主题字色像素 0 → 5335,再切回同样对称);像素判据是必需的,纯逻辑断言会被「切页顺带 refresh」掩盖,只有像素计数能钉死「不切页面也生效」;C 知识库警告色;Dapply_external_theme(设置页路径)与按钮路径行为一致。截图留档build/shots/theme-switch-{before-dark,after-light,after-dark}.png
Changed
- README 对外叙事校准(文档变更,2026-10-01):首屏定位从「碎片收集 → 分类归档 → 转任务/笔记 → 截图钉屏」改为「剪贴板自动留存 → 素材临时中转 → 截图钉屏对照」,并新增「它不适合下面这些期待」筛选表。依据=真实使用数据复核 + 代码级核实:①🔴**「碎片转任务」是假功能**——碎片右键菜单实测只有 查看详情/编辑/复制/存为笔记/加入知识库/添加至网址导航/钉为便签/导出 Obsidian/归类为/删除,
fragments_panel.py全文零add_task引用(add_task只在tasks_panel.py:375与card_window.py:1543被调用),而原 README 在定位句 / 竞争力表 / 功能描述三处都写了「转任务」→ 竞争力表拆成「碎片转笔记 / 入知识库 ✅」与「碎片转任务 ❌ 手动无入口(AI 文本工坊可自动提取待办)」两行,功能描述同步改口径;②真实使用画像(43 天,211 条碎片)显示 97% 来自剪贴板、内容中位数仅 32 字符(IP / 文件名 / 路径 / 临时链接,属中转态而非资产)、任务 10 / 笔记 3 / 知识库自 08-12 未动 → 原「第二大脑」叙事与真实行为错位,改为明说「它不打算做你的第二大脑」并把剪贴板提到第一位;③体积口径误导:「打包后约 75 MB」实为解压后体积,实际下载 zip 仅 31 MB,读者会误以为要下 75 MB → 改为「下载包 31 MB(解压后 75 MB)」;④顺带修正三处事实数字——数据层 JSON 7 → 8 个、UI 层「九个 Panel」→「十个页面(索引 0–9)」、竞争力表「快捕条 + 剪贴板」→「剪贴板自动收(为主)+ 快捕条 + 拖拽」(实测剪贴板 205 条 vs 快捕条 3 条)。英文段同步并新增 Not for 一句。★刻意保留不动:GitHub Stars 0 与「自用打磨 / 无外部用户验证」的诚实说明——这是 README 里最有说服力的部分