Skip to content

Commit 170b844

Browse files
authored
fix(windows): keep pet overlays transparent (#23)
Apply the skin only to complete Codex shell documents and clear stale theme DOM from transparent auxiliary windows. Adds renderer regression coverage and Windows QA notes.
1 parent 7786731 commit 170b844

5 files changed

Lines changed: 176 additions & 10 deletions

File tree

windows/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### 修复
66

7+
- 渲染层现在只在检测到完整 Codex 主界面壳层时启用皮肤;宠物等透明辅助窗口会主动清理主题背景与装饰节点,避免出现遮挡宠物的矩形背景框。
78
- 安装与 `-RestoreBaseTheme` 现在严格按 UTF-8 读取,保留原换行风格,并以无 BOM、同目录原子替换方式写回 `config.toml`,避免中文项目名称乱码或导致 Codex 无法启动。
89
- 遇到带 BOM/无 BOM 的 UTF-16、NUL 字符、无效 UTF-8 或写入期间被其他程序改动的配置时停止修改,不再静默转码或覆盖较新的内容。
910
- 安装会在当前注册包或 state 记录的旧 Codex 仍运行时明确提示先关闭;配置临时文件写完后会在原子替换前再次核对原始字节,进一步缩小并发覆盖窗口。
@@ -40,4 +41,5 @@
4041

4142
### 测试
4243

44+
- 增加渲染层辅助窗口回归测试,覆盖主窗口正常注入、透明辅助窗口清理残余样式,以及辅助目标随后成为完整主界面时可重新启用皮肤。
4345
- 增加中文项目路径、CRLF/LF、UTF-16 与歧义 TOML 拒绝、并发写检测、section 隔离、精确恢复、Appx/state 身份、状态归档、payload 构造、Browser ID 和不安全 CDP URL 的回归检查。

windows/assets/renderer-inject.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,27 @@
2121
existingStyle.dataset.dreamVersion = "1";
2222
}
2323

24+
const clearSkinDom = () => {
25+
document.documentElement?.classList.remove("codex-dream-skin");
26+
document.documentElement?.style.removeProperty("--dream-art");
27+
document.querySelectorAll(".dream-home").forEach((node) => node.classList.remove("dream-home"));
28+
document.querySelectorAll(".dream-home-shell").forEach((node) => node.classList.remove("dream-home-shell"));
29+
document.getElementById(STYLE_ID)?.remove();
30+
document.getElementById(CHROME_ID)?.remove();
31+
};
32+
2433
const ensure = () => {
2534
if (window.__CODEX_DREAM_SKIN_DISABLED__) return;
2635
const root = document.documentElement;
27-
if (!root) return;
36+
if (!root || !document.body) return;
37+
38+
const shellMain = document.querySelector("main.main-surface");
39+
const shellSidebar = document.querySelector("aside.app-shell-left-panel");
40+
if (!shellMain || !shellSidebar) {
41+
clearSkinDom();
42+
return;
43+
}
44+
2845
root.classList.add("codex-dream-skin");
2946
root.style.setProperty("--dream-art", `url("${artUrl}")`);
3047

@@ -39,14 +56,12 @@
3956
style.dataset.dreamVersion = "1";
4057
}
4158

42-
const shellMain = document.querySelector("main.main-surface") || document.querySelector("main");
4359
const home = document.querySelector('[role="main"]:has([data-testid="home-icon"])');
4460
for (const candidate of document.querySelectorAll('[role="main"].dream-home')) {
4561
if (candidate !== home) candidate.classList.remove("dream-home");
4662
}
4763
if (home) home.classList.add("dream-home");
4864

49-
if (!shellMain || !document.body) return;
5065
shellMain.classList.toggle("dream-home-shell", Boolean(home));
5166
let chrome = document.getElementById(CHROME_ID);
5267
if (!chrome || chrome.parentElement !== document.body) {
@@ -72,12 +87,7 @@
7287

7388
const cleanup = () => {
7489
window.__CODEX_DREAM_SKIN_DISABLED__ = true;
75-
document.documentElement?.classList.remove("codex-dream-skin");
76-
document.documentElement?.style.removeProperty("--dream-art");
77-
document.querySelectorAll(".dream-home").forEach((node) => node.classList.remove("dream-home"));
78-
document.querySelectorAll(".dream-home-shell").forEach((node) => node.classList.remove("dream-home-shell"));
79-
document.getElementById(STYLE_ID)?.remove();
80-
document.getElementById(CHROME_ID)?.remove();
90+
clearSkinDom();
8191
const state = window[STATE_KEY];
8292
state?.observer?.disconnect();
8393
if (state?.timer) clearInterval(state.timer);

windows/references/qa-inventory.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Sidebar: open a real task, then return to New Task.
1818
- Composer: type text, verify caret/readability, then clear it without sending.
1919
- Reload: use CDP `Page.reload`, wait, and confirm the injection marker returns.
20+
- Pet overlay: open a desktop pet and confirm its auxiliary window stays transparent with no skin background or decoration layer behind it.
2021
- Restore/reapply cycle: remove live skin, verify marker absent, apply again, verify marker present.
2122
- Update resilience: resolve the current `OpenAI.Codex` Appx location dynamically for launch. A versioned path saved for cleanup must be revalidated against the registered package full/family identity before any process is stopped.
2223
- Restart consent: an existing normal Codex window is never force-closed without explicit CLI authorization or shortcut confirmation.
@@ -42,6 +43,6 @@
4243

4344
## Automated checks
4445

45-
- `tests/run-tests.ps1`: strict UTF-8/no-BOM writes, UTF-16 rejection, LF/CRLF preservation, concurrent-write detection, exact backup/recovery, `[desktop]`-scoped restore, ambiguous TOML rejection, non-ASCII paths, Appx/state identity, argument quoting, payload construction, Browser ID, and loopback URL rejection.
46+
- `tests/run-tests.ps1`: strict UTF-8/no-BOM writes, UTF-16 rejection, LF/CRLF preservation, concurrent-write detection, exact backup/recovery, `[desktop]`-scoped restore, ambiguous TOML rejection, non-ASCII paths, Appx/state identity, argument quoting, payload construction, Browser ID, loopback URL rejection, and renderer isolation for transparent auxiliary windows.
4647
- `node --check` for the injector and renderer payload.
4748
- Live Windows signoff remains required for Store process ownership, restart consent, screenshot, and CDP closure.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs/promises";
3+
import path from "node:path";
4+
import vm from "node:vm";
5+
import { fileURLToPath } from "node:url";
6+
7+
const here = path.dirname(fileURLToPath(import.meta.url));
8+
const windowsRoot = path.resolve(here, "..");
9+
const template = await fs.readFile(path.join(windowsRoot, "assets", "renderer-inject.js"), "utf8");
10+
const payload = template
11+
.replace("__DREAM_CSS_JSON__", JSON.stringify(".fixture { color: blue; }"))
12+
.replace("__DREAM_ART_JSON__", JSON.stringify("data:image/png;base64,AA=="));
13+
14+
function createFixture({ shellPresent, staleSkin = false }) {
15+
const nodes = new Map();
16+
const rootClasses = new Set(staleSkin ? ["codex-dream-skin"] : []);
17+
const rootStyles = new Map(staleSkin ? [["--dream-art", "url(\"blob:stale\")"]] : []);
18+
const revokedUrls = [];
19+
let hasShell = shellPresent;
20+
21+
const makeClassList = (classes = new Set()) => ({
22+
add(value) { classes.add(value); },
23+
remove(value) { classes.delete(value); },
24+
toggle(value, enabled) {
25+
if (enabled) classes.add(value);
26+
else classes.delete(value);
27+
},
28+
});
29+
30+
const root = {
31+
classList: makeClassList(rootClasses),
32+
style: {
33+
setProperty(key, value) { rootStyles.set(key, value); },
34+
removeProperty(key) { rootStyles.delete(key); },
35+
},
36+
appendChild(node) {
37+
node.parentElement = root;
38+
nodes.set(node.id, node);
39+
},
40+
};
41+
const body = {
42+
appendChild(node) {
43+
node.parentElement = body;
44+
nodes.set(node.id, node);
45+
},
46+
};
47+
const shellMain = {
48+
classList: makeClassList(),
49+
getBoundingClientRect() {
50+
return { left: 290, top: 36, width: 990, height: 784 };
51+
},
52+
};
53+
const staleHome = { classList: makeClassList(new Set(["dream-home"])) };
54+
const staleShell = { classList: makeClassList(new Set(["dream-home-shell"])) };
55+
56+
const createElement = () => ({
57+
id: "",
58+
dataset: {},
59+
style: {},
60+
classList: makeClassList(),
61+
parentElement: null,
62+
textContent: "",
63+
innerHTML: "",
64+
setAttribute() {},
65+
remove() { nodes.delete(this.id); },
66+
});
67+
if (staleSkin) {
68+
const style = createElement();
69+
style.id = "codex-dream-skin-style";
70+
nodes.set(style.id, style);
71+
const chrome = createElement();
72+
chrome.id = "codex-dream-skin-chrome";
73+
nodes.set(chrome.id, chrome);
74+
}
75+
76+
const document = {
77+
documentElement: root,
78+
head: root,
79+
body,
80+
createElement,
81+
getElementById(id) { return nodes.get(id) ?? null; },
82+
querySelector(selector) {
83+
if (selector === "main.main-surface") return hasShell ? shellMain : null;
84+
if (selector === "aside.app-shell-left-panel") return hasShell ? {} : null;
85+
return null;
86+
},
87+
querySelectorAll(selector) {
88+
if (!staleSkin) return [];
89+
if (selector === ".dream-home") return [staleHome];
90+
if (selector === ".dream-home-shell") return [staleShell];
91+
return [];
92+
},
93+
};
94+
const context = {
95+
window: {},
96+
document,
97+
MutationObserver: class {
98+
observe() {}
99+
disconnect() {}
100+
},
101+
URL: {
102+
createObjectURL() { return "blob:fixture"; },
103+
revokeObjectURL(value) { revokedUrls.push(value); },
104+
},
105+
Blob,
106+
Uint8Array,
107+
atob,
108+
setInterval: () => 1,
109+
clearInterval: () => {},
110+
setTimeout: () => 2,
111+
clearTimeout: () => {},
112+
};
113+
114+
return {
115+
context,
116+
nodes,
117+
rootClasses,
118+
rootStyles,
119+
revokedUrls,
120+
setShellPresent(value) { hasShell = value; },
121+
};
122+
}
123+
124+
const main = createFixture({ shellPresent: true });
125+
const mainResult = vm.runInNewContext(payload, main.context);
126+
assert.equal(mainResult.installed, true);
127+
assert.equal(main.rootClasses.has("codex-dream-skin"), true);
128+
assert.equal(main.rootStyles.get("--dream-art"), 'url("blob:fixture")');
129+
assert.equal(main.nodes.has("codex-dream-skin-style"), true);
130+
assert.equal(main.nodes.has("codex-dream-skin-chrome"), true);
131+
assert.equal(main.context.window.__CODEX_DREAM_SKIN_STATE__.cleanup(), true);
132+
assert.equal(main.rootClasses.has("codex-dream-skin"), false);
133+
assert.equal(main.nodes.has("codex-dream-skin-style"), false);
134+
assert.equal(main.nodes.has("codex-dream-skin-chrome"), false);
135+
assert.deepEqual(main.revokedUrls, ["blob:fixture"]);
136+
137+
const auxiliary = createFixture({ shellPresent: false, staleSkin: true });
138+
const auxiliaryResult = vm.runInNewContext(payload, auxiliary.context);
139+
assert.equal(auxiliaryResult.installed, true);
140+
assert.equal(auxiliary.rootClasses.has("codex-dream-skin"), false);
141+
assert.equal(auxiliary.rootStyles.has("--dream-art"), false);
142+
assert.equal(auxiliary.nodes.has("codex-dream-skin-style"), false);
143+
assert.equal(auxiliary.nodes.has("codex-dream-skin-chrome"), false);
144+
145+
auxiliary.setShellPresent(true);
146+
auxiliary.context.window.__CODEX_DREAM_SKIN_STATE__.ensure();
147+
assert.equal(auxiliary.rootClasses.has("codex-dream-skin"), true);
148+
assert.equal(auxiliary.nodes.has("codex-dream-skin-style"), true);
149+
assert.equal(auxiliary.nodes.has("codex-dream-skin-chrome"), true);
150+
151+
console.log("PASS: renderer themes the Codex shell and preserves transparent auxiliary windows.");

windows/tests/run-tests.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ try {
314314
if ($LASTEXITCODE -ne 0) { throw 'Injector CDP self-test failed.' }
315315
& $node.Path (Join-Path $Root 'scripts\injector.mjs') --check-payload *> $null
316316
if ($LASTEXITCODE -ne 0) { throw 'Injector self-test failed.' }
317+
& $node.Path (Join-Path $PSScriptRoot 'renderer-inject.test.mjs')
318+
if ($LASTEXITCODE -ne 0) { throw 'Renderer auxiliary-window regression test failed.' }
317319

318320
Write-Host 'PASS: config transactions, restore scoping, state safety, argument quoting, and loopback CDP validation.'
319321
} finally {

0 commit comments

Comments
 (0)