From 595d7bd4499ed6e8acfbd3a9082d277ca0df06e6 Mon Sep 17 00:00:00 2001
From: starsstreaming <2946843254lw@gmail.com>
Date: Sat, 24 Oct 2026 20:33:10 +0800
Subject: [PATCH 1/5] fix(codex): preserve local dim restoration and composer
protection
---
...dex-startup-shadow-and-composer-surface.md | 30 ++++++++
packages/adapter-codex/src/payload.ts | 7 +-
.../adapter-codex/src/renderer/background.css | 11 +++
.../adapter-codex/src/renderer/console.js | 77 +------------------
packages/adapter-codex/src/renderer/dim.js | 45 +++++++++++
.../test/appearance.browser.test.js | 73 ++++++++++++++++++
6 files changed, 166 insertions(+), 77 deletions(-)
create mode 100644 docs/adr/0008-codex-startup-shadow-and-composer-surface.md
create mode 100644 packages/adapter-codex/src/renderer/dim.js
create mode 100644 packages/adapter-codex/test/appearance.browser.test.js
diff --git a/docs/adr/0008-codex-startup-shadow-and-composer-surface.md b/docs/adr/0008-codex-startup-shadow-and-composer-surface.md
new file mode 100644
index 0000000..b6416a2
--- /dev/null
+++ b/docs/adr/0008-codex-startup-shadow-and-composer-surface.md
@@ -0,0 +1,30 @@
+# ADR 0008:Codex 初始化阴影与输入区装饰层
+
+- 日期:2026-10-23
+- 状态:接受
+- 范围:Codex adapter;保持现有背景档案、存储键及其他客户端行为。
+
+## 问题与证据
+
+用户报告初始化阴影与设置不符,并提供输入框外围黑色矩形截图。
+现场 Codex 包版本为 26.1016.6573.0,CDP 与探索下的背景入口可识别。
+读取现场时保存值、API、根节点 CSS 变量及清单都为 46%,背景未活动;这次快照不代表已经复现真实客户端的启动时序。
+
+浏览器回归使用真实媒体载荷,复现两个初始化缺口:只有控制台负责恢复阴影,独立媒体启动不恢复;宿主替换 html.style 后,界面仍显示 46%,媒体阴影却回到 0%。
+现场还发现 composer 后有空的、不可交互、绝对定位的 bg-surface 元素;现有样式没有清除它的底色,与用户截图一致。生成的 CSS 模块哈希不作为适配契约。
+
+## 决定
+
+将阴影偏好的读取、校验、持久化及 CSS 同步移入独立 dim.js,媒体初始化前安装,控制台仅调用同一 API。继续使用 beauticode-dim 存储键与 0~1 值域,0 是有效设置,非法值回退自动。
+
+只监听 html 的 style 与 data-bc-dim-user 属性。幂等写入避免自身触发循环;重复安装复用同版本实例,替换版本释放旧监听。窗口关闭结束生命周期,清除媒体保留用户偏好供下次应用。自动滑块位置分别对应深色 0%、浅色 48%。
+
+只在背景活动时,将 main 内底部定位容器的空、不可交互 bg-surface 子装饰层设为透明。输入框、对话框和其他 surface 保留底色;清除背景恢复宿主样式。不引入通用 surface 透明规则。
+
+## 验证与边界
+
+appearance.browser.test.js 使用 Edge 实际渲染,覆盖首次恢复、根样式替换、零值、非法值、浅深色、清除偏好不重建舞台及输入区边界。通过 BEAUTICODE_PLAYWRIGHT_MODULE 指向已有 Playwright 运行时;没有运行时时显式跳过,不添加发布依赖。
+
+其他客户端现场均未运行,安装状态不能代表新版 DOM 验收。另一个已有五端浅色检查发现,四个非 DSH 客户端默认 48% 白色遮罩在纯黑壁纸上只有约 3.75:1 对比度,本次保留既有亮度策略,未将其宣称为通过。
+
+回滚可恢复安装目录 current.json 指向上一版本,再重启 beautiCode 辅助进程;无需重启或修改 Codex 二进制。源码回滚同时恢复 payload、console、CSS 并移除 dim.js,原存储键无需迁移。
diff --git a/packages/adapter-codex/src/payload.ts b/packages/adapter-codex/src/payload.ts
index a85148d..51c022e 100644
--- a/packages/adapter-codex/src/payload.ts
+++ b/packages/adapter-codex/src/payload.ts
@@ -10,16 +10,17 @@ export async function loadRendererSource(): Promise<{
runtimeIife: string;
consoleSource: string;
}> {
- const [cssText, runtimeIife, consoleJs, galleryJs, themeJs] = await Promise.all([
+ const [cssText, runtimeIife, consoleJs, galleryJs, themeJs, dimJs] = await Promise.all([
fs.readFile(path.join(here, "renderer", "background.css"), "utf8"),
fs.readFile(path.join(here, "renderer", "background-runtime.js"), "utf8"),
fs.readFile(path.join(here, "renderer", "console.js"), "utf8"),
fs.readFile(path.join(here, "renderer", "gallery.js"), "utf8"),
fs.readFile(path.join(here, "renderer", "theme.js"), "utf8"),
+ fs.readFile(path.join(here, "renderer", "dim.js"), "utf8"),
]);
// Resolve host appearance after media installs, including healthy re-injects.
- const themedRuntime = `((...args) => Promise.resolve((${runtimeIife})(...args)).then(result => { ${themeJs}\nreturn result; }))`;
- return { cssText, runtimeIife: themedRuntime, consoleSource: `${consoleJs}\n${galleryJs}` };
+ const themedRuntime = `((...args) => { ${dimJs}\nreturn Promise.resolve((${runtimeIife})(...args)).then(result => { ${themeJs}\nreturn result; }); })`;
+ return { cssText, runtimeIife: themedRuntime, consoleSource: `${dimJs}\n${consoleJs}\n${galleryJs}` };
}
/** 1×1 PNG. Video injects must not ship the live poster as a multi-MB data URL. */
diff --git a/packages/adapter-codex/src/renderer/background.css b/packages/adapter-codex/src/renderer/background.css
index 4690952..43dc121 100644
--- a/packages/adapter-codex/src/renderer/background.css
+++ b/packages/adapter-codex/src/renderer/background.css
@@ -267,6 +267,17 @@ html[data-bc-active="true"] body > #root main
background: transparent !important;
}
+/* Codex 26.1016: an empty, non-interactive surface behind the composer paints
+ a large black rectangle outside the rounded input. Target its layout role,
+ not the generated CSS-module hash or every bg-surface (which includes cards).
+ The data-composer-body and functional panels below retain their opaque fill. */
+html[data-bc-active="true"] body > #root main
+ .pointer-events-none.absolute.inset-x-0.bottom-0 >
+ .pointer-events-none.absolute.inset-x-0.bottom-0.bg-surface:empty {
+ background: transparent !important;
+ background-image: none !important;
+}
+
/* Home hero card chrome — keep interactive, drop solid fill. */
html[data-bc-active="true"] body > #root main
[role="main"]:has([data-testid="home-icon"])
diff --git a/packages/adapter-codex/src/renderer/console.js b/packages/adapter-codex/src/renderer/console.js
index 12b904c..9543421 100644
--- a/packages/adapter-codex/src/renderer/console.js
+++ b/packages/adapter-codex/src/renderer/console.js
@@ -1,6 +1,6 @@
(() => {
"use strict";
- const CONSOLE_REV = 12;
+ const CONSOLE_REV = 13;
if (window.__beauticodeConsoleRev === CONSOLE_REV && window.__beauticodeConsoleLoaded) {
try {
window.__beauticodeConsolePlace?.();
@@ -171,82 +171,10 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop{background:#f3f0e9;color:
let currentThemeId = "";
let themesExpanded = true;
- const DIM_STORAGE_KEY = "beauticode-dim";
- let userDim = null;
-
- function clampDim(value) {
- const n = Number(value);
- if (!Number.isFinite(n) || n < 0 || n > 1) return null;
- return n;
- }
-
- function readStoredDim() {
- try {
- const raw = globalThis.localStorage?.getItem(DIM_STORAGE_KEY);
- if (raw == null || raw === "") return null;
- return clampDim(raw);
- } catch {
- return null;
- }
- }
-
- function writeStoredDim(value) {
- try {
- if (value == null) globalThis.localStorage?.removeItem(DIM_STORAGE_KEY);
- else globalThis.localStorage?.setItem(DIM_STORAGE_KEY, String(value));
- } catch {
- /* private mode / quota */
- }
- }
-
- function applyUserDim(value) {
- const root = document.documentElement;
- if (value == null) {
- try {
- root.removeAttribute("data-bc-dim-user");
- root.style?.removeProperty?.("--bc-dim");
- } catch {
- /* ignore */
- }
- return null;
- }
- try {
- root.setAttribute("data-bc-dim-user", "true");
- root.style?.setProperty?.("--bc-dim", String(value));
- } catch {
- /* ignore */
- }
- return value;
- }
-
- function setUserDim(value) {
- const next = clampDim(value);
- if (next == null) return userDim;
- userDim = next;
- writeStoredDim(next);
- applyUserDim(next);
- return next;
- }
-
- function clearUserDim() {
- userDim = null;
- writeStoredDim(null);
- applyUserDim(null);
- return null;
- }
-
- userDim = readStoredDim();
- if (userDim != null) applyUserDim(userDim);
- globalThis.BeauticodeBackgroundDim = {
- get: () => userDim,
- set: setUserDim,
- clear: clearUserDim,
- };
-
function renderDim() {
const current = globalThis.BeauticodeBackgroundDim?.get?.() ?? null;
if (current == null) {
- dimSlider.value = String(AUTO_DIM_PERCENT);
+ dimSlider.value = String(isLightTone() ? AUTO_DIM_PERCENT : 0);
dimValue.textContent = "自动";
dimReset.hidden = true;
return;
@@ -476,6 +404,7 @@ body:not([data-ds-dark-theme]) #beauticode-console-pop{background:#f3f0e9;color:
// Host appearance can flip while the pop is closed; sampling it on open
// keeps a closed pop free of observers on a very large tree.
renderBlur();
+ renderDim();
void refresh();
}
}
diff --git a/packages/adapter-codex/src/renderer/dim.js b/packages/adapter-codex/src/renderer/dim.js
new file mode 100644
index 0000000..d683523
--- /dev/null
+++ b/packages/adapter-codex/src/renderer/dim.js
@@ -0,0 +1,45 @@
+// Shadow belongs to the media lifecycle, not the optional sidebar console.
+// Install before the first frame; Codex may replace html.style during startup.
+(() => {
+ const installed = globalThis.BeauticodeBackgroundDim;
+ if (installed?.rev === 1) { installed.refresh(); return; }
+ installed?.stop?.();
+ const root = document.documentElement;
+ const key = 'beauticode-dim';
+ function parse(value) {
+ if (value == null || String(value).trim() === '') return null;
+ const number = Number(value);
+ return Number.isFinite(number) && number >= 0 && number <= 1 ? number : null;
+ }
+ let value = null;
+ try { value = parse(localStorage.getItem(key)); } catch { /* Storage may be unavailable. */ }
+ function refresh() {
+ if (value == null) {
+ if (root.hasAttribute('data-bc-dim-user')) root.removeAttribute('data-bc-dim-user');
+ if (root.style.getPropertyValue('--bc-dim')) root.style.removeProperty('--bc-dim');
+ } else {
+ if (root.getAttribute('data-bc-dim-user') !== 'true') root.setAttribute('data-bc-dim-user', 'true');
+ if (root.style.getPropertyValue('--bc-dim') !== String(value)) root.style.setProperty('--bc-dim', String(value));
+ }
+ }
+ function persist() {
+ try {
+ if (value == null) localStorage.removeItem(key);
+ else localStorage.setItem(key, String(value));
+ } catch { /* Keep the live preference when storage is unavailable. */ }
+ refresh();
+ }
+ // Observe only the host-owned root attributes; idempotent writes prevent a
+ // mutation loop. Keep the preference across clear/reapply until window close.
+ const observer = new MutationObserver(refresh);
+ observer.observe(root, { attributes: true, attributeFilter: ['style', 'data-bc-dim-user'] });
+ globalThis.BeauticodeBackgroundDim = {
+ rev: 1,
+ get: () => value,
+ set(next) { const parsed = parse(next); if (parsed != null) { value = parsed; persist(); } return value; },
+ clear() { value = null; persist(); return null; },
+ refresh,
+ stop() { observer.disconnect(); },
+ };
+ refresh();
+})();
diff --git a/packages/adapter-codex/test/appearance.browser.test.js b/packages/adapter-codex/test/appearance.browser.test.js
new file mode 100644
index 0000000..45da6cb
--- /dev/null
+++ b/packages/adapter-codex/test/appearance.browser.test.js
@@ -0,0 +1,73 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { loadRendererSource, buildInjectionExpression } from '../dist/payload.js';
+
+const runtime = process.env.BEAUTICODE_PLAYWRIGHT_MODULE;
+const browserTest = runtime ? test : test.skip;
+const image = 'data:image/svg+xml,' + encodeURIComponent('');
+
+async function fixture(t, stored = '0.46', tone = 'dark') {
+ const { chromium } = await import(runtime);
+ const browser = await chromium.launch({ channel: 'msedge', headless: true });
+ t.after(() => browser.close());
+ const page = await browser.newPage();
+ await page.route('http://127.0.0.1:19335/**', route => route.fulfill({ body: `
`, contentType: 'text/html' }));
+ await page.goto('http://127.0.0.1:19335/');
+ await page.evaluate(value => { if(value !== null) localStorage.setItem('beauticode-dim',value); }, stored);
+ const source = await loadRendererSource();
+ await page.evaluate(buildInjectionExpression(source.runtimeIife, { generation: 1, media: 'image', imageDataUrl: image }, source.cssText));
+ return { page, source };
+}
+
+browserTest('saved shadow is restored before the first active background without a console', async t => {
+ const { page } = await fixture(t);
+ assert.equal(await page.locator('#beauticode-bg-stage').evaluate(n => getComputedStyle(n,'::after').backgroundColor), 'rgba(0, 0, 0, 0.46)');
+});
+
+browserTest('host root style replacement cannot desynchronise saved shadow from its setting', async t => {
+ const { page, source } = await fixture(t);
+ await page.addScriptTag({ content: source.consoleSource });
+ await page.evaluate(() => document.documentElement.setAttribute('style','--font-ui-size:14px'));
+ await page.waitForTimeout(30);
+ assert.equal(await page.locator('#beauticode-bg-stage').evaluate(n => getComputedStyle(n,'::after').backgroundColor), 'rgba(0, 0, 0, 0.46)');
+ assert.equal(await page.locator('.bc-dim-value').first().textContent(), '46%');
+});
+
+browserTest('zero, automatic and invalid stored shadow keep their intended appearance', async t => {
+ for (const [stored, tone, expected] of [
+ ['0', 'dark', 'rgba(0, 0, 0, 0)'],
+ [null, 'dark', 'rgba(0, 0, 0, 0)'],
+ ['invalid', 'light', 'rgba(255, 255, 255, 0.48)'],
+ ['0.46', 'light', 'rgba(255, 255, 255, 0.46)'],
+ ]) {
+ const { page, source } = await fixture(t, stored, tone);
+ assert.equal(await page.locator('#beauticode-bg-stage').evaluate(n => getComputedStyle(n,'::after').backgroundColor), expected);
+ await page.addScriptTag({ content: source.consoleSource });
+ const stage = await page.locator('#beauticode-bg-stage').elementHandle();
+ await page.evaluate(() => { BeauticodeBackgroundDim.set(0.7); BeauticodeBackgroundDim.clear(); });
+ await page.waitForTimeout(30);
+ assert.equal(await page.evaluate(() => localStorage.getItem('beauticode-dim')), null);
+ assert.equal(await page.evaluate(() => document.documentElement.style.getPropertyValue('--bc-dim')), '');
+ assert.equal(await stage.evaluate(n => n === document.getElementById('beauticode-bg-stage')), true);
+ await page.close();
+ }
+});
+
+browserTest('new composer decoration is transparent while composer and dialogs stay opaque', async t => {
+ const { page } = await fixture(t);
+ const bg = selector => page.locator(selector).evaluate(n => getComputedStyle(n).backgroundColor);
+ assert.equal(await bg('#decoration'), 'rgba(0, 0, 0, 0)');
+ assert.equal(await bg('[data-composer-body]'), 'rgb(31, 31, 31)');
+ assert.equal(await bg('[role=dialog]'), 'rgb(31, 31, 31)');
+ assert.equal(await bg('#unrelated'), 'rgb(24, 24, 24)');
+ await page.evaluate(() => window.__BEAUTICODE_BG__.dispose());
+ assert.equal(await bg('#decoration'), 'rgb(24, 24, 24)');
+});
From de97250e9b308a49afc3de1ec36d3b4e0a02cd58 Mon Sep 17 00:00:00 2001
From: starsstreaming <2946843254lw@gmail.com>
Date: Sat, 24 Oct 2026 20:33:10 +0800
Subject: [PATCH 2/5] fix(codex): retain native corners and themed wallpaper
surfaces
---
.../adapter-codex/src/renderer/background.css | 86 +++++++++++++++++--
.../adapter-codex/test/wallpaper-css.test.js | 17 ++++
2 files changed, 98 insertions(+), 5 deletions(-)
create mode 100644 packages/adapter-codex/test/wallpaper-css.test.js
diff --git a/packages/adapter-codex/src/renderer/background.css b/packages/adapter-codex/src/renderer/background.css
index 43dc121..11dd03a 100644
--- a/packages/adapter-codex/src/renderer/background.css
+++ b/packages/adapter-codex/src/renderer/background.css
@@ -198,7 +198,6 @@ html[data-bc-active="true"] aside[class*="_ResponsiveLeftPanel_"] {
background-color: transparent !important;
background-image: none !important;
border: 0 !important;
- border-radius: 0 !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
@@ -232,7 +231,6 @@ html[data-bc-active="true"] body > #root main {
background: transparent !important;
background-image: none !important;
border: 0 !important;
- border-radius: 0 !important;
box-shadow: none !important;
}
@@ -285,7 +283,6 @@ html[data-bc-active="true"] body > #root main
> div:first-child
> div:first-child {
border: 0 !important;
- border-radius: 0 !important;
background: transparent !important;
background-image: none !important;
box-shadow: none !important;
@@ -361,8 +358,7 @@ html[data-bc-active="true"][data-bc-fish="true"] #beauticode-video-input {
palette — by id (works for already-mounted UI) and by the data-bc-ui marker
(covers surfaces added later). */
html[data-bc-active="true"] :is([role="dialog"], [role="alertdialog"],
- [role="menu"], [role="listbox"], [role="tooltip"],
- [data-radix-popper-content-wrapper], [data-composer-body],
+ [role="menu"], [role="listbox"], [data-composer-body],
form:has(textarea),
form:has([contenteditable="true"])):not([data-bc-ui], [data-bc-ui] *,
#beauticode-console, #beauticode-console *,
@@ -371,3 +367,83 @@ html[data-bc-active="true"] :is([role="dialog"], [role="alertdialog"],
#beauticode-name-dialog, #beauticode-name-dialog *) {
background-color: var(--bc-readable-fill) !important;
}
+
+/* Positioning shells must not paint square plates behind rounded panels. */
+html[data-bc-active="true"] [data-radix-popper-content-wrapper] {
+ background: transparent !important;
+}
+
+/* Floating surface plates follow the host palette. Ordinary tooltips and
+ beautiCode UI retain their own colors. */
+html[data-bc-active="true"]
+ :is([data-overlay-owner], [data-overlay-owner] *)
+ :is(.bg-surface, .bg-surface-secondary, .bg-surface-elevated,
+ .bg-surface-elevated-secondary):not([role="tooltip"], [role="tooltip"] *):not(
+ [data-bc-ui], [data-bc-ui] *, #beauticode-console, #beauticode-console *,
+ #beauticode-console-pop, #beauticode-console-pop *,
+ #beauticode-gallery, #beauticode-gallery *,
+ #beauticode-name-dialog, #beauticode-name-dialog *) {
+ background-color: var(--bc-readable-fill) !important;
+ background-image: none !important;
+}
+html[data-bc-active="true"] [data-overlay-owner]:is(
+ .bg-surface, .bg-surface-secondary, .bg-surface-elevated,
+ .bg-surface-elevated-secondary):not([role="tooltip"]):not(
+ [data-bc-ui], [data-bc-ui] *, #beauticode-console, #beauticode-console *,
+ #beauticode-console-pop, #beauticode-console-pop *,
+ #beauticode-gallery, #beauticode-gallery *,
+ #beauticode-name-dialog, #beauticode-name-dialog *) {
+ background-color: var(--bc-readable-fill) !important;
+}
+
+/* Sidebar peeks are cards represented as tooltips. Preserve the native
+ radius and clip the painted direct surface wrapper, not its controls. */
+html[data-bc-active="true"] [role="tooltip"][data-overlay-owner="slate-sidebar"]:not(
+ [data-bc-ui], [data-bc-ui] *, #beauticode-console, #beauticode-console *,
+ #beauticode-console-pop, #beauticode-console-pop *,
+ #beauticode-gallery, #beauticode-gallery *,
+ #beauticode-name-dialog, #beauticode-name-dialog *) {
+ background-color: var(--bc-readable-fill) !important;
+ overflow: hidden !important;
+ box-shadow: 0 12px 32px #0003,
+ 0 0 0 1px color-mix(in srgb, currentColor 12%, transparent) !important;
+}
+html[data-bc-active="true"] [role="tooltip"][data-overlay-owner="slate-sidebar"]:not(
+ [data-bc-ui], [data-bc-ui] *, #beauticode-console, #beauticode-console *,
+ #beauticode-console-pop, #beauticode-console-pop *,
+ #beauticode-gallery, #beauticode-gallery *,
+ #beauticode-name-dialog, #beauticode-name-dialog *) >
+ :is(.bg-surface, .bg-surface-secondary, .bg-surface-elevated, .bg-surface-elevated-secondary) {
+ border-radius: inherit !important;
+ background-color: inherit !important;
+}
+
+/* Native content route contract observed on 26.1016 (Sites): workspace slot,
+ full-height shell and centered tab panels. Nested reading cards are absent
+ from these selectors. No generated class names or viewport-size guesses. */
+html[data-bc-active="true"] main
+ .outline-none.flex.h-full.min-h-0.min-w-0.flex-1.flex-col >
+ .bg-surface.flex.h-full.min-h-0.flex-col:has([role="tabpanel"],
+ > .contents > .h-full.min-h-0.flex-1.overflow-y-auto >
+ .flex.min-h-full.w-full.flex-col > .border-transparent h1),
+html[data-bc-active="true"] main
+ .outline-none.flex.h-full.min-h-0.min-w-0.flex-1.flex-col >
+ .bg-surface.flex.h-full.min-h-0.flex-col
+ .mx-auto.bg-surface:has(> [role="tabpanel"]),
+html[data-bc-active="true"] main
+ .outline-none.flex.h-full.min-h-0.min-w-0.flex-1.flex-col >
+ .bg-surface.flex.h-full.min-h-0.flex-col
+ [role="tabpanel"].mx-auto.bg-surface {
+ background-color: transparent !important;
+ background-image: none !important;
+}
+
+/* The native header plate is a pseudo-element on the scroll shell, not h1.
+ Anchor to its observed layout hierarchy rather than the generated _shell_ hash. */
+html[data-bc-active="true"] main
+ .outline-none.flex.h-full.min-h-0.min-w-0.flex-1.flex-col .contents >
+ .h-full.min-h-0.flex-1.overflow-y-auto > .flex.min-h-full.w-full.flex-col >
+ .border-transparent:has(> .mx-auto.w-full h1):not([data-bc-ui], [data-bc-ui] *)::before {
+ background-color: transparent !important;
+ background-image: none !important;
+}
diff --git a/packages/adapter-codex/test/wallpaper-css.test.js b/packages/adapter-codex/test/wallpaper-css.test.js
new file mode 100644
index 0000000..74213d5
--- /dev/null
+++ b/packages/adapter-codex/test/wallpaper-css.test.js
@@ -0,0 +1,17 @@
+import assert from "node:assert/strict";
+import fs from "node:fs/promises";
+import test from "node:test";
+const css = await fs.readFile(new URL("../src/renderer/background.css", import.meta.url), "utf8");
+test("wallpaper preserves the host sidebar, main and hero corner radii", () => {
+ assert.doesNotMatch(css, /border-radius:\s*0\s*!important/);
+});
+test("ordinary tooltips and popper positioning wrappers are not painted as panels", () => {
+ const panels = css.slice(css.indexOf("/* Functional panels"), css.indexOf("background-color: var(--bc-readable-fill)", css.indexOf("/* Functional panels")));
+ assert.doesNotMatch(panels, /role="tooltip"|data-radix-popper-content-wrapper/);
+});
+test("overlay protection follows the host palette and excludes owned UI", () => {
+ assert.match(css, /data-overlay-owner/);
+ assert.doesNotMatch(css, /rgba\(25[035],\s*25[0259],\s*25[345],\s*0\.9/);
+ assert.doesNotMatch(css, /main aside\s*\{/);
+ assert.doesNotMatch(css, /_ComposerLayoutRoot_|empty-state-pag|class\*="_shell_"/);
+});
From 7345b5332568657ff4b4e61a01f80cf8dff044f2 Mon Sep 17 00:00:00 2001
From: starsstreaming <2946843254lw@gmail.com>
Date: Sat, 24 Oct 2026 20:33:10 +0800
Subject: [PATCH 3/5] refactor(codex): own media and guest wallpaper lifecycles
---
packages/adapter-codex/src/payload.ts | 23 +-
.../src/renderer/background-runtime.js | 2311 +++--------------
.../src/renderer/guest-install.js | 25 +
.../src/renderer/guest-policy.js | 57 +
.../src/renderer/guest-wallpaper.js | 168 ++
.../adapter-codex/src/renderer/runtime-api.js | 160 ++
.../src/renderer/runtime-controls.js | 306 +++
.../src/renderer/runtime-image.js | 122 +
.../src/renderer/runtime-media-wait.js | 36 +
.../src/renderer/runtime-sources.js | 103 +
.../src/renderer/runtime-video-element.js | 148 ++
.../src/renderer/runtime-video-lifecycle.js | 197 ++
.../src/renderer/runtime-video.js | 313 +++
.../src/renderer/runtime-working.js | 251 ++
.../test/adapter-session.test.js | 239 ++
.../test/adapter-startup.test.js | 455 ++++
packages/adapter-codex/test/adapter.test.js | 744 ------
.../test/guest-wallpaper.test.js | 242 ++
.../test/image-lifecycle.test.js | 42 +
.../test/packaged-renderer.test.js | 20 +
.../test/renderer-contract.test.js | 135 +
.../test/video-lifecycle.test.js | 46 +
.../test/wallpaper.browser.test.js | 168 ++
23 files changed, 3593 insertions(+), 2718 deletions(-)
create mode 100644 packages/adapter-codex/src/renderer/guest-install.js
create mode 100644 packages/adapter-codex/src/renderer/guest-policy.js
create mode 100644 packages/adapter-codex/src/renderer/guest-wallpaper.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-api.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-controls.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-image.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-media-wait.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-sources.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-video-element.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-video-lifecycle.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-video.js
create mode 100644 packages/adapter-codex/src/renderer/runtime-working.js
create mode 100644 packages/adapter-codex/test/adapter-session.test.js
create mode 100644 packages/adapter-codex/test/adapter-startup.test.js
create mode 100644 packages/adapter-codex/test/guest-wallpaper.test.js
create mode 100644 packages/adapter-codex/test/image-lifecycle.test.js
create mode 100644 packages/adapter-codex/test/packaged-renderer.test.js
create mode 100644 packages/adapter-codex/test/renderer-contract.test.js
create mode 100644 packages/adapter-codex/test/video-lifecycle.test.js
create mode 100644 packages/adapter-codex/test/wallpaper.browser.test.js
diff --git a/packages/adapter-codex/src/payload.ts b/packages/adapter-codex/src/payload.ts
index 51c022e..5411daa 100644
--- a/packages/adapter-codex/src/payload.ts
+++ b/packages/adapter-codex/src/payload.ts
@@ -18,8 +18,27 @@ export async function loadRendererSource(): Promise<{
fs.readFile(path.join(here, "renderer", "theme.js"), "utf8"),
fs.readFile(path.join(here, "renderer", "dim.js"), "utf8"),
]);
- // Resolve host appearance after media installs, including healthy re-injects.
- const themedRuntime = `((...args) => { ${dimJs}\nreturn Promise.resolve((${runtimeIife})(...args)).then(result => { ${themeJs}\nreturn result; }); })`;
+ const factories = {
+ createWorkingState: "runtime-working.js",
+ createPlaybackControls: "runtime-controls.js",
+ createMediaSources: "runtime-sources.js",
+ createMediaWaiter: "runtime-media-wait.js",
+ createImageRuntime: "runtime-image.js",
+ createVideoLifecycle: "runtime-video-lifecycle.js",
+ createVideoElementBinding: "runtime-video-element.js",
+ createVideoRuntime: "runtime-video.js",
+ createBackgroundApi: "runtime-api.js",
+ createGuestPolicy: "guest-policy.js",
+ createGuestController: "guest-wallpaper.js",
+ createGuestOwner: "guest-install.js",
+ };
+ const modules = await Promise.all(Object.entries(factories).map(async ([name,file]) =>
+ `const ${name} = ${await fs.readFile(path.join(here,"renderer",file),"utf8")};`));
+ // Factories remain lexical to the injected payload. Only the document-scoped
+ // guest owner survives media handoffs; modules expose operations, not state bags.
+ const themedRuntime = `((...args) => { ${dimJs}\n${modules.join("\n")}
+ const guest = createGuestOwner({createController:createGuestController,policy:createGuestPolicy()});
+ return Promise.resolve((${runtimeIife})(...args)).then(result => { ${themeJs}\nreturn result; }); })`;
return { cssText, runtimeIife: themedRuntime, consoleSource: `${dimJs}\n${consoleJs}\n${galleryJs}` };
}
diff --git a/packages/adapter-codex/src/renderer/background-runtime.js b/packages/adapter-codex/src/renderer/background-runtime.js
index abc5579..3ab6818 100644
--- a/packages/adapter-codex/src/renderer/background-runtime.js
+++ b/packages/adapter-codex/src/renderer/background-runtime.js
@@ -20,2003 +20,370 @@
* So media MUST arrive as data: or blob: URLs.
*/
((cssText, imageDataUrl, videoConfig, generation, imageUrl, forceRebuild, imageBlob) => {
- const STAGE_ID = "beauticode-bg-stage";
- const STYLE_ID = "beauticode-bg-style";
- const VIDEO_INPUT_ID = "beauticode-video-input";
- const IMAGE_INPUT_ID = "beauticode-image-input";
- const gen = Number(generation) || 0;
- const wantImageBlob = Boolean(imageBlob);
-
- const toChineseMediaError = (error) => {
- const message = String(error?.message || error || "").trim();
- if (/^(?:failed to fetch|fetch failed|fail fetch)$/i.test(message)) {
- return "未发现注入CDP的Codex进程";
- }
- if (/media fetch failed/i.test(message)) {
- return "背景媒体加载失败。";
- }
- if (/data url fetch failed/i.test(message)) {
- return "背景数据加载失败。";
- }
- if (/missing (?:media )?url|no video source/i.test(message)) {
- return "背景媒体地址缺失。";
- }
- if (/malformed data url/i.test(message)) {
- return "背景数据地址格式错误。";
- }
- if (/stale generation/i.test(message)) {
- return "背景版本已过期,正在重新加载。";
- }
- return message || "背景媒体加载失败。";
- };
-
- const root = document.documentElement;
- const previous = window.__BEAUTICODE_BG__;
-
- // ---- Same-generation short-circuit (critical anti-flash) ----
- // The tray watch loop re-publishes the active payload every ~1s. Rebuilding
- // the stage on every poll creates stacked