Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 196 additions & 1 deletion packages/adapter-codex/src/renderer/background-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,190 @@
const root = document.documentElement;
const previous = window.__BEAUTICODE_BG__;

// Full-page ChatGPT routes (Plugins, Skills, Library, Plans) live in an
// Electron <webview>. Host CSS cannot cross that document boundary, so clear
// the guest's opaque shell tokens from the host through executeJavaScript.
// Controls keep their own borders and foregrounds; only page-sized surfaces
// are made transparent so the full-window wallpaper remains the base layer.
const GUEST_STYLE_ID = "beauticode-guest-wallpaper-style";
const GUEST_CSS = `
html, body, body > #root, body > #__next {
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
}
`;
const guestExpression = `(() => {
const id = ${JSON.stringify(GUEST_STYLE_ID)};
let style = document.getElementById(id);
if (!style) {
style = document.createElement("style");
style.id = id;
(document.head || document.documentElement).appendChild(style);
}
style.textContent = ${JSON.stringify(GUEST_CSS)};
const ATTR = "data-beauticode-guest-cleared";
const paint = () => {
if (!document.body) return;
const vw = window.innerWidth || 1;
const vh = window.innerHeight || 1;
const visit = (el, depth) => {
if (!(el instanceof HTMLElement) || depth > 6) return;
if (!el.hasAttribute(ATTR)) {
const rect = el.getBoundingClientRect();
if (rect.width >= vw * 0.9 && rect.height >= vh * 0.9) {
const bg = getComputedStyle(el).backgroundColor;
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") {
el.setAttribute(ATTR, el.style.backgroundColor || "");
el.style.setProperty("background-color", "transparent", "important");
}
}
}
for (const child of el.children) visit(child, depth + 1);
};
for (const child of document.body.children) visit(child, 0);
};
paint();
if (!window.__beauticodeGuestObserver) {
const observer = new MutationObserver(() => {
if (window.__beauticodeGuestPaintTimer) return;
window.__beauticodeGuestPaintTimer = setTimeout(() => {
window.__beauticodeGuestPaintTimer = 0;
try { paint(); } catch (_) {}
}, 300);
});
observer.observe(document.documentElement, { childList: true, subtree: true });
window.__beauticodeGuestObserver = observer;
}
return true;
})()`;
const clearGuestExpression = `(() => {
document.getElementById(${JSON.stringify(GUEST_STYLE_ID)})?.remove();
try {
window.__beauticodeGuestObserver?.disconnect();
window.__beauticodeGuestObserver = null;
if (window.__beauticodeGuestPaintTimer) {
clearTimeout(window.__beauticodeGuestPaintTimer);
window.__beauticodeGuestPaintTimer = 0;
}
} catch (_) {}
document.querySelectorAll("[data-beauticode-guest-cleared]").forEach((el) => {
const prev = el.getAttribute("data-beauticode-guest-cleared");
el.removeAttribute("data-beauticode-guest-cleared");
if (prev) el.style.backgroundColor = prev;
else el.style.removeProperty("background-color");
});
return true;
})()`;
const guestTransparencyAllowed = (webview) => {
try {
const src = webview.getAttribute("src") || "";
return !src.startsWith("codex-sandbox:");
} catch (_) {
return false;
}
};
const installGuestTransparency = (webview) => {
if (
!webview ||
!guestTransparencyAllowed(webview) ||
typeof webview.executeJavaScript !== "function"
) {
return;
}
try {
const result = webview.executeJavaScript(guestExpression);
if (result && typeof result.catch === "function") result.catch(() => {});
} catch (_) {}
if (!webview.__beauticodeGuestTransparencyBound) {
webview.__beauticodeGuestTransparencyBound = true;
const refresh = () => installGuestTransparency(webview);
webview.__beauticodeGuestTransparencyRefresh = refresh;
try { webview.addEventListener("dom-ready", refresh); } catch (_) {}
try { webview.addEventListener("did-finish-load", refresh); } catch (_) {}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
};
const syncGuestTransparency = () => {
try {
document.querySelectorAll('webview:not([src^="codex-sandbox:"])')
.forEach(installGuestTransparency);
} catch (_) {}
};
const clearGuestTransparency = () => {
try {
document.querySelectorAll("webview").forEach((webview) => {
if (!webview.__beauticodeGuestTransparencyBound) return;
if (typeof webview.executeJavaScript === "function") {
try {
const result = webview.executeJavaScript(clearGuestExpression);
if (result && typeof result.catch === "function") {
result.catch(() => {});
}
} catch (_) {}
}
const refresh = webview.__beauticodeGuestTransparencyRefresh;
if (refresh) {
try { webview.removeEventListener("dom-ready", refresh); } catch (_) {}
try {
webview.removeEventListener("did-finish-load", refresh);
} catch (_) {}
}
try {
webview.__beauticodeGuestTransparencyBound = false;
delete webview.__beauticodeGuestTransparencyRefresh;
} catch (_) {}
});
} catch (_) {}
};
const isBackgroundActive = () =>
root.getAttribute("data-bc-active") === "true";
let guestSyncTimer = null;
const scheduleGuestSync = () => {
if (!isBackgroundActive() || guestSyncTimer) return;
guestSyncTimer = setTimeout(() => {
guestSyncTimer = null;
if (isBackgroundActive()) syncGuestTransparency();
}, 200);
};
let guestObserver = null;
try {
guestObserver = new MutationObserver((records) => {
for (const record of records) {
if (
record.type === "attributes" &&
record.target instanceof Element &&
record.target.tagName === "WEBVIEW"
) {
scheduleGuestSync();
return;
}
for (const node of record.addedNodes) {
if (!(node instanceof Element)) continue;
if (node.tagName === "WEBVIEW" || node.querySelector("webview")) {
scheduleGuestSync();
return;
}
}
}
});
guestObserver.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["src"],
});
} catch (_) {}
const stopGuestWatchers = () => {
try {
guestObserver?.disconnect();
} catch (_) {}
guestObserver = null;
if (guestSyncTimer) {
clearTimeout(guestSyncTimer);
guestSyncTimer = null;
}
};

// ---- 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 <video> nodes + poster flashes.
Expand Down Expand Up @@ -111,6 +295,7 @@
}
} catch (_) {}
}
if (isBackgroundActive()) syncGuestTransparency();
return { skipped: true, generation: gen, reason: "same-generation" };
}
} catch (_) {
Expand Down Expand Up @@ -789,6 +974,7 @@
};

const setAttrs = () => {
const wasActive = root.getAttribute("data-bc-active") === "true";
const handoffReady =
videoEnabled &&
!videoFailed &&
Expand Down Expand Up @@ -816,9 +1002,11 @@
root.removeAttribute("data-bc-tone");
fishMode = false;
stopWorkingWatch();
if (wasActive) clearGuestTransparency();
return;
}
root.setAttribute("data-bc-active", "true");
if (!wasActive) syncGuestTransparency();
// Keep media=video during handoff so CSS does not snap back to image-only
// while the previous frame is still covering the stage.
const media =
Expand Down Expand Up @@ -1722,6 +1910,7 @@
teardownImageBlob();
releaseHandoff();
clearStage();
clearGuestTransparency();
resolvedPoster = null;
stopWorkingWatch();
setAttrs();
Expand Down Expand Up @@ -1863,6 +2052,7 @@
// Always stop working-state watchers; the next generation starts its own.
// Do not leave MutationObservers / intervals from a superseded runtime.
stopWorkingWatch();
stopGuestWatchers();
const handoff = Boolean(opts && opts.handoff);
if (!handoff) {
teardownVideo();
Expand All @@ -1889,6 +2079,7 @@
} catch (_) {}
if (!handoff) {
window.__BEAUTICODE_THEME__?.stop();
clearGuestTransparency();
clearStage();
root.removeAttribute("data-bc-active");
root.removeAttribute("data-bc-media");
Expand Down Expand Up @@ -2011,7 +2202,11 @@
// every Codex video import then sat in verify until the console timed out.
return Promise.resolve()
.then(apply)
.then(() => ({ installed: true, generation: gen }))
.then(() => {
if (isBackgroundActive()) syncGuestTransparency();
else clearGuestTransparency();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return { installed: true, generation: gen };
})
.catch((err) => {
if (isCurrent()) {
imageFailed = true;
Expand Down
107 changes: 102 additions & 5 deletions packages/adapter-codex/src/renderer/background.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -274,7 +272,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;
Expand Down Expand Up @@ -350,8 +347,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 *,
Expand All @@ -360,3 +356,104 @@ html[data-bc-active="true"] :is([role="dialog"], [role="alertdialog"],
#beauticode-name-dialog, #beauticode-name-dialog *) {
background-color: var(--bc-readable-fill) !important;
}

/* A tooltip keeps the host's own dark surface and light label; forcing the
readable fill here left near-white text on white. The Radix popper wrapper
only positions a rounded panel, so painting it leaves a square plate behind
an otherwise correct menu. */
html[data-bc-active="true"] [data-radix-popper-content-wrapper] {
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
}

/* Full-page ChatGPT routes mount in an Electron <webview>. Keep the host frame
clear so the wallpaper stays the window base; the runtime also clears the
guest document's own opaque surfaces. */
html[data-bc-active="true"] body > #root
webview:not([src^="codex-sandbox:"]) {
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
}

/* Composer chrome: Codex paints a tinted band behind the input and a tinted
notice above it. Drop that wash so the wallpaper stays the base layer; the
input body keeps its own white card. */
html[data-bc-active="true"] body > #root main
[class*="_background_"][class*="_surface_"],
html[data-bc-active="true"] body > #root main
[class*="_ComposerLayoutRoot_"] {
background-color: transparent !important;
background-image: none !important;
}

html[data-bc-active="true"] body > #root main aside {
background-color: rgba(255, 255, 255, 0.9) !important;
background-image: none !important;
}

/* Scroll fade above the composer: Codex fades into the themed surface, which
leaves a tinted strip over the wallpaper. Let the wallpaper carry it. */
html[data-bc-active="true"] body > #root main
[class*="_background_"][class*="_fade_"] {
background-image: none !important;
background-color: transparent !important;
}

/* Content routes (Library, Sites, Images) paint a page-sized tinted surface
plus a header plate on the shell's ::before. Keep both on the wallpaper. */
html[data-bc-active="true"] body > #root main
div.bg-surface:has([class*="_shell_"]) {
background-color: transparent !important;
background-image: none !important;
}

html[data-bc-active="true"] body > #root main
[class*="_shell_"]::before {
background-color: transparent !important;
background-image: none !important;
}

html[data-bc-active="true"] body > #root main
div.bg-surface:has([class*="empty-state-pag"]) {
background-color: transparent !important;
background-image: none !important;
}

/* Hover previews and other floating cards keep a light plate so the card
separates from the page behind it. Only overlay surfaces match, so a page
stays on the wallpaper after navigating there. */
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"] *) {
background-color: rgba(253, 252, 254, 0.94) !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"]) {
background-color: rgba(253, 252, 254, 0.94) !important;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
background-image: none !important;
}

/* Project and page peeks: the card's inner wrapper paints a square white
plate over the rounded shell, so the preview reads as a hard rectangle.
Inherit the radius and clip the wrapper; a hairline ring keeps the rounded
silhouette visible on light wallpapers. */
html[data-bc-active="true"]
[role="tooltip"][data-overlay-owner="slate-sidebar"] {
border-radius: 18px !important;
background-color: rgba(250, 249, 253, 0.97) !important;
overflow: hidden !important;
box-shadow:
0 18px 44px rgba(40, 22, 54, 0.22),
0 0 0 1px rgba(74, 35, 95, 0.1) !important;
}

html[data-bc-active="true"]
[role="tooltip"][data-overlay-owner="slate-sidebar"] > * {
border-radius: inherit !important;
}