Add docs/agent-handoff (backlog, execution state, verification, platform plan, per-task evidence), repo AGENTS.md, and architecture notes updated for the dual-renderer design.
8.4 KiB
P2-03 证据:WebView2 原生窗口遮挡层(重命名遮罩)
任务:修复 RenameOverlay 被 WebView2 原生子窗口压住的确定性缺陷,审计同类遮罩,只修可确认的原生窗口遮挡。
结论:RenameOverlay 已重写为独立顶层透明 Tool 窗(与 AttachmentPreviewOverlay 同一验证过的模式);审计确认它是唯一受影响的遮罩,其余全部本就是顶层窗口,未改动。自动化目标测试全绿。
根因(源码级)
core/webview2.py(L10-11 注释 + 子窗口发现实现):WebView2 的Chrome_WidgetWin_*是主窗口 HWND 的原生子 HWND(EnumChildWindows 轮询发现,SetBoundsAndZoomFactor 定位)。- Windows 上原生子 HWND 永远绘制在其父 HWND 内所有 Qt 渲染内容之上(Qt 绘入主窗口 backing store,原生子窗在 z 序更高)。
- 旧
RenameOverlay是bg_widget的子控件(QWidget(parent=bg_widget)+setGeometry(parent.rect())):遮罩与卡片都是主窗口内的 Qt 绘制 → 在聊天区(WebView2 所在区域)内被原生 webview 窗盖住:遮罩不暗、卡片被压。 - 唯一可盖住它的结构:独立顶层窗口(独立 HWND)+ 逐像素 alpha(
WA_TranslucentBackground)。AttachmentPreviewOverlay早已按此模式修复(其 docstring 明文记录同一缺陷),RenameOverlay是遗留的旧模式。
修改(ui/views/main_window.py,仅 RenameOverlay 类,L1363 起整类替换)
| 项 | 旧 | 新 |
|---|---|---|
| 窗口类型 | bg_widget 子控件 |
顶层 FramelessWindowHint | Tool(独立 HWND,拥有者=主窗口,Windows 上默认浮于拥有者之上,不入任务栏) |
| 透明 | 无(fillRect 半透明灰) | WA_TranslucentBackground + paintEvent 半透明灰(逐像素 alpha) |
| 覆盖范围 | parent.rect()(客户区内嵌) |
主窗口客户区:mapToGlobal(main.rect().topLeft()) + main.rect().size() —— 不含系统标题栏/边框,标题栏与窗口控制保持可操作 |
| 跟随 | resizeEvent(子控件自动跟随) |
eventFilter 挂主窗口:Move → move;Resize / WindowStateChange(最大化/还原;DPI 变化时 Qt 对主窗合成 move+resize,同路跟随)→ 客户区几何重同步 + 卡片重居中;Close/Hide → 关闭遮罩 |
| 入场动画 | QGraphicsOpacityEffect(顶层窗不可靠) | windowOpacity 属性动画 150ms(同附件预览层) |
| 行为 | 点空白/✕/取消 关闭、输入全选、Enter 提交 | 全部保留,新增 Esc 关闭(keyPressEvent);confirm() 空标题不 emit |
| 释放 | deleteLater |
close_overlay():removeEventFilter(main) + main.activateWindow()/raise_()(焦点回主窗)+ deleteLater;WA_DeleteOnClose;_closed 幂等防重入 |
| 卡片拖拽 | 有(限父窗内) | 保留(限客户区内) |
_rename_session 调用点未改(仍 RenameOverlay(current_title, self.bg_widget);parent.window() 取主窗口)。无新增辅助函数(几何同步逻辑与附件预览层各 10 行,不构成"真实重复",未抽公共函数)。
同类遮罩审计(只修可复现者)
| 遮罩/弹窗 | 位置 | 窗口类型 | 结论 |
|---|---|---|---|
AttachmentPreviewOverlay |
L53 | 顶层 Tool + WA_TranslucentBackground + eventFilter 跟随(frameGeometry) |
不受影响(已是正确模式,本次修复的参照) |
SettingsWindow |
L424 | 顶层 FramelessWindowHint(独立窗,内部虚化遮罩是其子控件) |
不受影响(独立 HWND,天然在 webview 之上) |
SessionContextPopup |
L1267 | Popup | FramelessWindowHint |
不受影响(Popup 为独立顶层原生窗) |
ModelSelectPopup |
L1624 | Popup | FramelessWindowHint(L1649) |
不受影响 |
SessionModePopup |
L2245 | Popup | FramelessWindowHint(L2260) |
不受影响 |
PdfModePopup |
L2368 | Popup | FramelessWindowHint(L2392) |
不受影响 |
RenameOverlay |
L1363 | 受影响,已修复(唯一) |
未把普通 popup/dialog 重写成统一框架(硬约束)。
自动化验证(本机 Windows 11 10.0.26200 x64,.venv CPython 3.10.21)
新建 tests/diag_rename_overlay.py(隔离临时 DB,offscreen,34 项断言,EXIT=0):
RESULT: 34/34 -> ALL PASS
R0 遮罩创建且为顶层窗口
R1 结构:isWindow / window() is self(非内嵌子控件)/ Tool / Frameless /
WA_TranslucentBackground / WA_DeleteOnClose / 主窗口未设透明
R2 几何:覆盖客户区左上角与尺寸(±2px)/ 卡片居中 / 输入框初始全选 / 预填旧标题
R3 跟随:主窗 move(+150,+80)→精确跟随 / resize(1200x700)→尺寸同步+卡片重居中 /
WindowStateChange 分支(最大化/还原同路)不崩溃且几何仍正确
R4 行为:Enter 提交→DB+侧栏标题更新 / 空标题 confirm 不改标题 /
Esc 关闭 / 点空白关闭 / ✕ 关闭 / 取消关闭 / 非确认关闭不改标题
R5 释放:顶层窗口消失、无残留 rename_form、主窗口存活可用
R6 焦点回主窗(offscreen 软检查,INFO 记录)
目标测试 + 回归(均 EXIT=0):
| 套件 | 结果 |
|---|---|
tests/diag_rename_overlay.py |
34/34 ALL PASS |
tests/smoke_offscreen.py(QtWebEngine 路径) |
8/8 ALL PASS |
tests/smoke_copy_session.py(侧栏/Popup 链路) |
ALL PASS |
tests/smoke_bash_panel.py |
140 项 ALL PASS |
tests/run_tests.py |
41 passed, 0 failed |
真机 WebView2 验收(用户走查步骤)
真机 WebView2 自动化被有意放弃:core/webview2.py 的 get_environment() 含 taskkill /F /IM msedgewebview2.exe(会杀掉用户其他 WebView2 应用进程)且 SDK 怪癖要求默认共享 profile(无法重定向到临时目录)——无人值守自动化运行该路径风险不可接受。
【2026-09-17 更新】本机 WebView2 加载阻塞已解除(core/webview2.py 加 byte[] 回落,见 evidence/P1-03.md):主程序现已能在本机以真实 WebView2 启动(Runtime 153.0.4234.32、controller ready、index.html NavigationCompleted、stderr 无错)。下表人工清单现可在本机真实 WebView2 模式下执行。
机制保证(与生产已验证的 AttachmentPreviewOverlay 完全同构):新遮罩是独立顶层 HWND(WS_EX_TOOLWINDOW + 拥有者=主窗口);Windows z 序规则下,拥有者窗口之上的顶层窗永远绘制在拥有者的原生子 HWND(WebView2 Chrome_WidgetWin_*)之上,与 offscreen 平台无关。
人工验收清单(Windows 桌面,真实 WebView2 模式运行主程序后):
- 会话右键 → 重命名:遮罩盖住整个客户区(含聊天区 webview,webview 变暗),标题栏(含最小化/最大化/关闭)仍可点。
- 拖主窗口 / 拉边框缩放 / 最大化 / 还原:遮罩与卡片全程贴合客户区、卡片保持居中。
- 多显示器间拖主窗 / 改缩放比后重开遮罩:几何正确(mapToGlobal 路径)。
- Esc / 点空白 / ✕ / 取消 → 遮罩消失、焦点回主窗、侧栏可继续操作;Enter 或确定 → 标题更新。
- 连续开/关 5 次:无残留遮罩、无卡顿、任务栏无新增图标。
教训(跨压缩持久)
- 【P2-03 发现】offscreen/无真实事件循环时
processEvents()不处理DeferredDelete:deleteLater()的控件必须显式QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)才会真正删除(探针证实:仅 processEvents 循环后对象仍 alive)。生产事件循环常驻不受影响,但所有 offscreen 测试的"已删除"断言前必须冲刷 DeferredDelete。 - 【P2-03 教训】PyQt6
setGeometry无(QPoint, QSize)重载 → 构造QRect(tl, size);QTest.mouseClick(widget, button, modifier, pos)第 3 参是 modifier(易误当 pos)。 - 【P2-03 发现】
core/webview2.pyget_environment()含taskkill /F /IM msedgewebview2.exe+ 共享默认 profile 不可重定向 → 无人值守自动化不得走真实 WebView2 启动路径;真机验收走人工清单。 - 【P2-03 结构判据】"顶层窗可带 owner parent":
setWindowFlags(Tool|Frameless)后parentWidget()仍可非 None(owner 关系),判据是isWindow()/window() is self,不是parentWidget() is None。
修改文件
ui/views/main_window.py(仅RenameOverlay类整类替换,~170 行;调用点未改)tests/diag_rename_overlay.py(新,34 项断言)