状态:代码已交付(2026-08-23,两项真机验收 P0 + 浮窗交互/架构升级同版交付) 依据:用户对话 2026-08-23(① 采集中点「🗕 浮窗化」→ 额外出现空白界面 + 全应用不可交互,ASR 仍正常; ② 精修工作台打开即崩:
Cannot read properties of undefined (reading 'split'),栈 W2/E5 = renderMd/RefineWorkbench) 前置:v0.12.1(OCR 回归修复)真机验收中;v0.12.2(笔记页信息架构重构)规划未启动,顺延为后续版本候选
两个 P0 真机验收雷同版修复:
- 浮窗/覆盖层窗口创建死锁(wry#583):同步 command 在 WebView2 IPC 回调(主线程)内执行
WebviewWindowBuilder::build()——CreateCoreWebView2ControllerWithOptions完成回调依赖主线程派发, 而主线程正阻塞在回调内等待它(循环等待)。症状与"空白界面 + 全应用不可点击、ASR 照常"完全对应 (引擎跑独立线程池;Win32 窗口先创建后 webview 初始化卡死 → 空白)。修复:窗口命令全部 async (Tauri 官方文档要求的模式,官方注释原文:"On Windows, this function deadlocks when used in a synchronous command and event handlers … useasynccommands and separate threads")。 - 精修工作台契约漏网(serde camelCase):
WorkbenchData是commands_ai_refine.rs中唯一缺失#[serde(rename_all = "camelCase")]的结构体——Rust 输出rule_markdown(snake_case),前端按ruleMarkdown读取恒为undefined→renderMd(undefined).split崩溃。**修复:回填 rename 契约- Rust 序列化单测 + 前端防御(
?? ""兜底)**。
- Rust 序列化单测 + 前端防御(
同时交付浮窗交互层(P1:拖拽+位置记忆+边缘吸附、面板/字幕条双形态、点击穿透锁定、 置顶开关、透明度、回主窗升级)与架构层(P2:常驻预创建、窗口状态收敛、capabilities 拆分)。
| # | 项目 | 类型 | 落点 |
|---|---|---|---|
| 1 | 浮窗/覆盖层窗口创建死锁(wry#583) | P0 修复 | commands_window.rs / commands_overlay.rs——建窗/关窗命令 async 化 |
| 2 | 精修工作台 undefined.split 崩溃 | P0 修复 | commands_ai_refine.rs(serde rename_all)+ RefineWorkbench.tsx(防御)+ 单测 |
| 3 | 浮窗双形态 + 拖拽/吸附/持久化 | P1 交互 | useFloatWindow.ts(新 hook)+ utils/floatWindow.ts(纯函数)+ CaptureFloatPanel.tsx 重写 |
| 4 | 点击穿透锁定 + 主窗解锁 + 置顶/透明度 | P1 交互 | 同上 + ClassroomPage.tsx 三态按钮(浮窗化 ⇄ 收起 ⇄ 解锁) |
| 5 | 回主窗升级(show+focus 主窗,浮窗保留) | P1 交互 | show_main_window 命令 |
| 6 | 浮窗常驻预创建 + 状态收敛 + capabilities 拆分 | P2 架构 | app_setup.rs(预创建)+ AppState.float_ui + capabilities/{float,overlay}.json |
| 7 | 文档与治理 | 伴随 | 本版本文档 + CHANGELOG + 索引 + 版本号 0.12.3 |
- 调用链:前端
invoke("open_capture_float")→ WebView2WebMessageReceived主线程派发 → Tauri 在该回调内同步执行 command(commands_window.rs原为#[tauri::command] pub fn)→WebviewWindowBuilder::build()需要创建第二个 WebView2 控件。 - 死锁点:wry 0.55.1
create_controller调用CreateCoreWebView2ControllerWithOptions后用webview2_com::wait_with_pump(rx)同步阻塞等待完成回调;该回调需主线程派发,而主线程正阻塞 在回调内等它 → 循环等待(wry#583:"deadlock on the create_controller function (the second closure is never called)";维护者结论:"document that it must use async or create thread to spawn another window on Windows")。 - 症状映射:tao 窗口先创建并显示(空白)→ webview 初始化卡死 → 主线程永久阻塞 → 主窗按钮不可点 (Win32 消息/点击全走主线程);ASR/OCR 跑独立引擎线程池不受影响 → "ASR 仍正常"。
- 同源隐患:
commands_overlay.rs的 M3 覆盖层窗口是同一种模式(同步 command + builder), 本版一并修复(v0.12.0 真机验收时已发现但未执行覆盖层路径,属同类雷)。 - 证据:真实运行包(release exe 内嵌 brotli asset)反编译核对 + tauri 2.11.5 源码文档警告
(
webview_window.rsL56-59)+ wry#583 issue 原文。
refine_workbench返回的WorkbenchData顶层键为 snake_case(rule_markdown/refined_markdown), 前端WorkbenchData类型与读取均为 camelCase(ruleMarkdown)→ 运行时undefined。RefineWorkbench.tsx渲染入口renderMd(wb.ruleMarkdown)无防御 →undefined.split("\n")崩。- 漏检原因:同模块所有兄弟结构体(AiRefineResult/CostEstimate/RefineEstimateView)均带
#[serde(rename_all = "camelCase")],唯独 WorkbenchData(v0.11.5 新增)遗漏;该面板未进 v0.11.5 真机验收路径 → "真机验收待执行" 的又一实证。 - 附带说明:嵌套
SectionDiff保持 snake_case 是刻意契约(首次出现即定,前端类型注释"勿改"), 本次 rename 只作用于顶层键,不影响嵌套字段。
- 双形态:面板(360×240 全功能)⇄ 字幕条(360×44 只读近况),Esc 或 ⤡/⤢ 切换;
形态联动程序化窗口尺寸(
resizable=false不影响setSize)。 - 拖拽 + 位置记忆 + 边缘吸附:顶部空白处拖拽(
startDragging);移动结束 250ms 去抖 → 边缘吸附(左/右/上/下 ≤8px 贴边)+ 位置持久化(localStorage,浮窗/主窗同源共享免 IPC); 挂载时按当前工作区钳制坐标(多屏变化防窗口"丢失")。 - 点击穿透锁定:🔓/🔒 切换
set_ignore_cursor_events——只读悬浮不拦截鼠标(看视频零遮挡); 锁定态浮窗自身不可点,解锁路径在主窗(按钮变「🔓 解锁浮窗」/ Ctrl+Shift+F 同语义)。 - 置顶开关(📌/📍)+ 透明度滑杆(35%–100%,钳制防越界)。
- 回主窗升级:
show_main_window——显示 + 还原 + 聚焦主窗,浮窗保留(不再销毁)。 ClassroomPage浮窗按钮三态语义(浮窗化 ⇄ 收起 ⇄ 解锁),状态经float_state查询 +float:state事件订阅(Rust 单一来源)。
- 常驻预创建:setup 预创建隐藏浮窗(P2-10:打开秒显、点击期零建窗风险);失败幂等回落为 打开时懒创建(open_capture_float 兜底)。close 语义从销毁改为隐藏(保留事件订阅)。
- 状态收敛:浮窗 UI 状态(locked/topmost)收进
AppState.float_ui(set_ignore_cursor_events无 getter 必须自存);全部窗口操作集中在commands_window.rs(单文件职责:open/close/locked/ topmost/state/show_main)。 - capabilities 拆分:
default.json(main 仅保留)→float.json/overlay.json(各自最小权限、无 dialog)。
RefineWorkbench.tsx:ruleMarkdown ?? ""、refinedMarkdown ?? null、sections ?? []、stats ?? {0,0,0}——后端字段缺失/契约漂移时渲染不崩(AGENTS.md 维度 4)。
- Rust:
workbench_data_serializes_camel_case_top_level单测(顶层键 camelCase + 嵌套 SectionDiff 保持 snake_case 的契约锚定);cargo check --all-targets通过(0 error,既有 warning 不变)。 - 前端:
tsc --noEmit零错误;vitest run81 通过(新增floatWindow.test.ts14 项: 钳制/吸附边界/偏好往返/损坏回退/透明度钳制/部分字段合并)。 - 真机验收路径(待执行):
- 浮窗:采集中 Ctrl+Shift+F → 浮窗显示内容、主窗按钮仍可点;拖拽移角落自动吸附;重启后位置记忆; Esc 切换字幕条;🔒 后主窗「解锁浮窗」恢复;滑杆透明度生效;停止采集浮窗隐藏;再开秒显。
- 覆盖层:图文采集框选全链路(与浮窗同因修复,一并验收)。
- 精修工作台:任意会话打开工作台 → 规则版/精修版双栏渲染正常;无精修时右栏"尚未精修"提示。
- 独占全屏限制:Windows 独占全屏(DXGI exclusive)下 alwaysOnTop 不可见(系统级限制), 全屏观看需走无边框窗口化——浮窗异常态提示文案未做(记需求池,真机验收后按反馈门控)。
- 常驻浮窗内存:预创建常驻一个 WebView2(约 40-80MB)换取秒开——P2-10 决策在文、可回退为懒创建。
- 事件双订阅:主窗与浮窗各自订阅 live:*(两 webview 独立状态);评估后判定收益低 (事件低频 × 状态一致——同源同事件),未改共享管线;若未来出现漂移再收敛为 Rust 侧摘要转发。
- v0.12.2 顺延:笔记页信息架构重构(三栏+收件箱动线)规划未启动,作为 v0.12.4 候选。
- 市场调研:浮窗方案谱系(独立置顶子窗口 ✅ / 点击穿透只读叠加层 / acrylic 材质 / 系统级 Overlay ❌ YAGNI / 托盘化 / 位置记忆吸边)——竞品对标:Granola(转录浮窗指示器)、OBS Fullscreen Projector Always-On-Top + 点击穿透实践、Fluent Screen Recorder(悬浮控制条)。
- 依据链路:用户浮窗 bug 报告 → 源码证据链(tauri-runtime-wry
send_user_message主线程内联路径 / wrycreate_controllerwait_with_pump / wry#583 / tauri 官方文档警告)→ async 修复。 - ADR:无(行为修复 + 交互增强,架构方向未变;常驻浮窗为已裁决 P2-10 落地)。