Skip to content

Commit 2d1b507

Browse files
committed
docs(versions): v0.12.3 版本文档与索引、CHANGELOG
1 parent da555b3 commit 2d1b507

3 files changed

Lines changed: 136 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55

66
## [Unreleased] - 2026-08-21
77

8-
### v0.12.2 笔记页信息架构重构(三栏 + 收件箱动线 + 路由信息收敛,2026-08-23 交付,详见 [docs/versions/v0.12.2.md](docs/versions/v0.12.2.md))
8+
### v0.12.3 浮窗死锁修复 + 浮窗交互/架构升级 + 精修工作台契约修复(2026-08-23 交付,详见 [docs/versions/v0.12.3.md](docs/versions/v0.12.3.md))
9+
10+
- **P0 浮窗/覆盖层窗口创建死锁(wry#583)**:同步 command 在 WebView2 IPC 回调(主线程)内执行 `WebviewWindowBuilder::build()`——`CreateCoreWebView2ControllerWithOptions` 完成回调需主线程派发而主线程阻塞在回调内等它(循环等待)。症状:点「浮窗化」→ 空白窗 + 全应用不可点击(ASR 引擎线程独立不受影响)。修复:建窗/关窗命令全部 async(Tauri 官方要求模式),`commands_window.rs` / `commands_overlay.rs` 四个命令 + 注释锚定
11+
- **P0 精修工作台 undefined.split 崩溃**:`WorkbenchData` 缺失 `#[serde(rename_all = "camelCase")]`(同模块唯一漏网)→ 前端 `ruleMarkdown` 读到 undefined → `renderMd(undefined).split` 崩。修复:回填契约 + Rust 序列化单测(含嵌套 SectionDiff 保持 snake_case 锚定)+ 前端 `?? ""` 防御
12+
- **浮窗交互层(P1)**:面板/字幕条双形态(Esc/⤡⤢ 切换)+ 拖拽移动 + 位置记忆(localStorage)+ 边缘吸附(≤8px)+ 点击穿透锁定(主窗解锁)+ 置顶开关 + 透明度滑杆(35–100%)+ 回主窗改为显示聚焦且浮窗保留;主窗按钮三态(浮窗化 ⇄ 收起 ⇄ 解锁)
13+
- **浮窗架构层(P2)**:setup 预创建常驻(隐藏,秒开 + 点击期零建窗风险;失败回落懒创建)+ 窗口状态收敛(AppState.float_ui 单一来源 + float:state 事件)+ capabilities 拆分(default/float/overlay,浮窗与覆盖层去 dialog 权限)
14+
- 验证:`cargo test`(新增契约单测)/ `cargo check --all-targets` / `tsc --noEmit` 零错误 / `vitest` 81 通过(新增 floatWindow 14 项:钳制/吸附/偏好往返/损坏回退/透明度钳制)
15+
16+
### v0.12.2 笔记页信息架构重构(三栏 + 收件箱动线 + 路由信息收敛,2026-08-23 规划定稿、未启动——顺延为后续版本候选,详见 [docs/versions/v0.12.2.md](docs/versions/v0.12.2.md))
917

1018
- **三栏布局(P0 交互债)**:笔记页从"550px 一栏塞三职"重构为三栏分工——GroupSidebar(240px 组筛选/快速记录/收件箱入口)+ NoteListView(320px 常驻中部)+ NoteReadingView(右栏);组行单击=仅过滤(消除"过滤+展开"双动作歧义);搜索/标签过滤时中部列表原位切换,布局不变
1119
- **收件箱动线(P0 产品缺口)**:碎片二元论转正——碎片=原料不是短笔记;收件箱恒常首项(待处理计数)只装碎片,未归组笔记在「全部笔记」(两种实体两条动线);碎片卡三出口:✍ 升为笔记(轻确认:标题预填首句可改 + 归组下拉默认未归组 → 右侧自动打开新笔记闭环可见)/ ⚙ 升为闪卡(幂等可重复触发)/ 🗑 删除(二次确认);空态引导三种归宿

‎docs/versions/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
| v0.11.7 | [v0.11.7.md](./v0.11.7.md) — 图文会话:截屏导入 + 跨链路采集体验同步(kind 字段 + 5 命令 + 框选遮罩;排期于 v0.11.6 之后) | 版本文档(规划中 2026-08-22) |
3232
| v0.12.0 | [v0.12.0.md](./v0.12.0.md) — 图文与窗口捕获重塑:正文源多态 + WGC 抗遮挡 + 系统级截屏覆盖层 + DeepSeek 默认化 + vision 精修 + 采集浮窗化(M1-M7,七项改进修正五个断裂) | 版本文档(规划定稿 2026-08-23;**M1-M7 全部已交付**,真机验收待执行) |
3333
| v0.12.1 | [v0.12.1.md](./v0.12.1.md) — OCR 引擎回归修复:oar-ocr auto-download 误关(v0.12.0 构建修复连带)+ 引擎就绪状态真实化(engine_ready 四处落地)+ 图文会话讲者分析误报修复 | 版本文档(代码已交付 2026-08-23,真机验收回归路径待执行) |
34-
| v0.12.2 | [v0.12.2.md](./v0.12.2.md) — 笔记页信息架构重构:三栏布局(组筛选/笔记列表/阅读编辑)+ 收件箱动线(二元论碎片→升笔记补口子)+ 路由信息收敛(ⓘ 弹层)+ 新建零对话框 | 版本文档(规划定稿 2026-08-23) |
34+
| v0.12.2 | [v0.12.2.md](./v0.12.2.md) — 笔记页信息架构重构:三栏布局(组筛选/笔记列表/阅读编辑)+ 收件箱动线(二元论碎片→升笔记补口子)+ 路由信息收敛(ⓘ 弹层)+ 新建零对话框 | 版本文档(规划定稿 2026-08-23;**未启动,顺延为后续版本候选**) |
35+
| v0.12.3 | [v0.12.3.md](./v0.12.3.md) — 真机验收两雷同修:浮窗/覆盖层窗口创建死锁(sync 命令主线程建窗,wry#583)+ 精修工作台 serde camelCase 契约漏网(ruleMarkdown undefined.split 崩);附带浮窗交互层(双形态/拖拽吸附/穿透锁定/置顶/透明度/回主窗)与架构层(常驻预创建/状态收敛/capabilities 拆分) | 版本文档(代码已交付 2026-08-23,真机验收待执行) |
3536

3637
## 沉淀规则
3738

‎docs/versions/v0.12.3.md‎

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

0 commit comments

Comments
 (0)