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: `
+
+
+
+
Dialog
Other surface
+
`, 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