From 4fbab37bbc792e7802467e212f6d9f1984ddeddc Mon Sep 17 00:00:00 2001 From: StephenHsu Date: Sun, 26 Jul 2026 06:46:17 -0400 Subject: [PATCH 1/3] Fix themed layout seams and native scrolling --- macos/CHANGELOG.md | 9 + macos/VERSION | 2 +- macos/assets/dream-skin.css | 596 ++++++++++++++---- macos/assets/renderer-inject.js | 8 +- macos/assets/selectors.json | 2 +- macos/package.json | 2 +- macos/scripts/common-macos.sh | 2 +- macos/scripts/injector.mjs | 19 +- macos/tests/run-tests.sh | 4 +- macos/tests/window-readiness.test.mjs | 22 + runtime/dream-skin.css | 596 ++++++++++++++---- runtime/renderer-inject.js | 8 +- tools/renderer-runtime.test.mjs | 146 ++++- tools/selectors.json | 2 +- windows/VERSION | 2 +- windows/assets/dream-skin.css | 596 ++++++++++++++---- windows/assets/renderer-inject.js | 8 +- windows/assets/selectors.json | 2 +- windows/scripts/injector.mjs | 26 +- .../tests/injector-window-readiness.test.mjs | 20 +- 20 files changed, 1716 insertions(+), 356 deletions(-) diff --git a/macos/CHANGELOG.md b/macos/CHANGELOG.md index 349b5101..8ac945a2 100644 --- a/macos/CHANGELOG.md +++ b/macos/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 1.5.6 — 2026-07-26 + +### 修复 + +- 移除会覆盖原生标题栏的主题标语与定位规则,不再调整返回、前进、侧栏和窗口控件的原生布局,恢复左右工具栏的安全间距、点击区域与可读性。 +- 将宽幅任务背景重构为单一画布:画作只存在于最底层,连续阅读遮罩在同一主平面过渡,不再创建独立的右侧图片窗口;输入框同时改为完整、独立的高层 surface,避免背景、遮罩、裁切或层级造成右端断裂。 +- 补齐浅色主题对原生面板、会话摘要、输入、弹窗、提示状态和选中态的语义色映射,避免深色宿主 token 穿透后产生低对比度文字或表面。 +- 注入验证现在会检查原生会话滚动容器仍可滚动;主题若覆盖其 `overflow`、裁切或交互能力,将安全失败而不是提交一个无法使用的活动状态。 + ## 1.5.5 — 2026-07-25 ### 修复 diff --git a/macos/VERSION b/macos/VERSION index 9075be49..eac1e0ad 100644 --- a/macos/VERSION +++ b/macos/VERSION @@ -1 +1 @@ -1.5.5 +1.5.6 diff --git a/macos/assets/dream-skin.css b/macos/assets/dream-skin.css index bd112ec0..a3a580ed 100644 --- a/macos/assets/dream-skin.css +++ b/macos/assets/dream-skin.css @@ -59,6 +59,70 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* One-canvas layout contract. Native window chrome and content geometry + remain authoritative; artwork must adapt to the product, never reserve + layout space at the expense of typing or reading comfort. */ + --titlebar-safe-left: max(env(safe-area-inset-left), 0px); + --titlebar-safe-right: max(env(safe-area-inset-right), 0px); + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + /* Semantic design tokens. Theme packages provide the primitive palette; + native components consume these roles so material, state and accessibility + decisions stay consistent instead of scattering alpha values. */ + --ds-canvas: var(--ds-bg); + --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); + --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); + --reading-veil: linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); + --ds-surface: var(--ds-panel); + --message-surface: rgb(var(--ds-panel-rgb) / .94); + --ds-elevated-surface: var(--ds-panel); + --ds-popover: rgb(var(--ds-panel-rgb) / .988); + --ds-input: rgb(var(--ds-panel-rgb) / .985); + --composer-surface: rgb(var(--ds-panel-rgb) / .92); + --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); + --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); + --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); + --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); + --ds-focus: var(--ds-accent); + --ds-border: var(--ds-line); + --ds-text-primary: var(--ds-text); + --ds-text-secondary: var(--ds-muted); + --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); + --ds-danger: #ff9a8f; + --ds-danger-surface: #4b1f22; + --ds-danger-foreground: #fff0ed; + --ds-danger-border: #d87970; + --ds-success: #80c79b; + --ds-success-surface: #203d2d; + --ds-success-foreground: #e6f4e9; + --ds-success-border: #72a782; + --ds-warning-surface: #4c351c; + --ds-warning-foreground: #fff0cf; + --ds-warning-border: #cf984b; + --ds-info-surface: #223842; + --ds-info-foreground: #edf7fa; + --ds-info-border: #7899a6; + --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); + --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); + --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); + --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); + --ds-zone-shadow: + 0 7px 18px rgb(var(--ds-bg-rgb) / .09), + inset 0 1px rgb(var(--ds-panel-rgb) / .82); + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); + --ds-shadow-elevated: + 0 16px 38px rgb(var(--ds-bg-rgb) / .20), + 0 2px 8px rgb(var(--ds-bg-rgb) / .12); + --ds-motion-fast: 120ms; + --ds-motion-standard: 160ms; + --ds-motion-reveal: 210ms; + --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -107,6 +171,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); + --ds-danger: #9c3d3a; + --ds-danger-surface: #f8e6e3; + --ds-danger-foreground: #6f2523; + --ds-danger-border: #b85650; + --ds-success: #45624d; + --ds-success-surface: #e4efe5; + --ds-success-foreground: #28543a; + --ds-success-border: #5f856b; + --ds-warning-surface: #f8edd7; + --ds-warning-foreground: #674515; + --ds-warning-border: #aa7a32; + --ds-info-surface: #e8eef1; + --ds-info-foreground: #284754; + --ds-info-border: #6f8b97; + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); + --ds-shadow-elevated: + 0 18px 42px rgb(var(--ds-bg-rgb) / .16), + 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -115,6 +197,131 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); + --vscode-inputValidation-errorBackground: var(--ds-danger-surface); + --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); + --vscode-inputValidation-errorBorder: var(--ds-danger-border); + --color-token-input-validation-error-background: var(--ds-danger-surface); + --color-token-input-validation-error-foreground: var(--ds-danger-foreground); + --color-token-input-validation-error-border: var(--ds-danger-border); + --color-token-error-foreground: var(--ds-danger-foreground); + --color-token-editor-error-foreground: var(--ds-danger-foreground); +} + +/* Codex can remain electron-dark while a theme explicitly requests a light + shell. Bridge the native token family to the active skin so task cards, + inputs, secondary copy and icons do not keep dark-mode values on a light + wallpaper. This is component-scoped token remapping, never whole-page + opacity. */ +html[data-dream-skin="active"][data-dream-shell="light"] { + --vscode-foreground: var(--ds-text); + --vscode-descriptionForeground: var(--ds-muted); + --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); + --color-background-panel: var(--ds-panel); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); + --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); + --color-token-conversation-body: var(--ds-muted); + --color-token-conversation-summary-leading: var(--ds-muted); + --color-token-conversation-summary-trailing: var(--ds-muted); + --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); + --color-token-description-foreground: var(--ds-muted); + --color-token-list-hover-background: var(--ds-hover); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* The scheduled-task detail side panel uses a separate inline panel surface + variable and input token family. Reinforce the light-shell bridge at its + native form boundary so the prompt, detail and schedule cards cannot retain + electron-dark surfaces or pale input copy. */ +html[data-dream-skin="active"][data-dream-shell="light"] + [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { + color: var(--ds-text); + --color-background-panel: var(--ds-input); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: var(--ds-muted); + --color-token-description-foreground: var(--ds-muted); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* Native toasts use one generic foreground token across four different + surfaces. Resolve the actual host state classes into independent semantic + palettes before styling the shared alert component. */ +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-dropdown-background"] { + --ds-alert-surface: var(--ds-info-surface); + --ds-alert-foreground: var(--ds-info-foreground); + --ds-alert-border: var(--ds-info-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { + --ds-alert-surface: var(--ds-success-surface); + --ds-alert-foreground: var(--ds-success-foreground); + --ds-alert-border: var(--ds-success-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { + --ds-alert-surface: var(--ds-warning-surface); + --ds-alert-foreground: var(--ds-warning-foreground); + --ds-alert-border: var(--ds-warning-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { + --ds-alert-surface: var(--ds-danger-surface); + --ds-alert-foreground: var(--ds-danger-foreground); + --ds-alert-border: var(--ds-danger-border); +} + +html[data-dream-skin="active"] [role="alert"].alert-root { + background-color: var(--ds-alert-surface, var(--ds-popover)) !important; + border-color: var(--ds-alert-border, var(--ds-border)) !important; + color: var(--ds-alert-foreground, var(--ds-text)) !important; + --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { + color: var(--ds-alert-foreground, var(--ds-text)) !important; + opacity: .76 !important; +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { + opacity: 1 !important; +} + +/* Active tool/agent summaries render duplicated shimmer text with hard-coded + alpha and a white sweep. Keep the base label readable and tint only its + moving overlay with the skin accent; no ancestor opacity is changed. */ +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { + color: var(--ds-muted) !important; + -webkit-text-fill-color: var(--ds-muted) !important; +} + +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { + color: var(--ds-accent) !important; + -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -130,12 +337,10 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; - --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; - --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -218,9 +423,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] aside.app-shell-left-panel { background: linear-gradient(180deg, - rgb(var(--ds-panel-rgb) / .98), - rgb(var(--ds-bg-rgb) / .96)) !important; - border: 1px solid var(--ds-line) !important; + var(--ds-sidebar), + rgb(var(--ds-bg-rgb) / .975)) !important; + border: 1px solid var(--ds-border) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -230,12 +435,53 @@ html[data-dream-skin="active"] aside.app-shell-left-panel { html[data-dream-skin="active"] aside.app-shell-left-panel nav { background: transparent !important; } +/* Recent and other semantic lists may read as quiet raised sections. The + primary navigation stays on the native flat canvas: wrapping its split + fixed/scrolling DOM in a faux card creates the broken right edge called out + in the screenshot and relies on brittle nth-child geometry. */ +html[data-dream-skin="active"] aside.app-shell-left-panel + nav section:has([role="list"]) { + box-sizing: border-box !important; + margin-inline: 8px !important; + padding: 7px 6px 8px !important; + border: 1px solid var(--ds-zone-border) !important; + border-radius: 14px !important; + background: var(--ds-zone-surface-raised) !important; + box-shadow: var(--ds-zone-shadow) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel + nav section:has([role="list"]) > div > div:first-child { + margin-bottom: 3px !important; + padding-bottom: 5px !important; + border-bottom: 1px solid var(--ds-zone-divider) !important; +} + +/* Use the existing 16px native resize target as the visual boundary. The line + is non-interactive and never narrows or covers the resizer hit area. */ +html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]::before { + content: "" !important; + position: absolute !important; + top: 0 !important; + bottom: 0 !important; + left: 8px !important; + width: 1px !important; + background: var(--ds-zone-border) !important; + box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]:hover::before { + background: rgb(var(--ds-accent-rgb) / .52) !important; +} + html[data-dream-skin="active"] aside.app-shell-left-panel button, html[data-dream-skin="active"] aside.app-shell-left-panel a { color: var(--ds-text) !important; - transition: background-color .18s cubic-bezier(.22, 1, .36, 1), - border-color .18s cubic-bezier(.22, 1, .36, 1), - color .18s cubic-bezier(.22, 1, .36, 1) !important; + transition: background-color var(--ds-motion-standard) var(--ds-ease-out), + border-color var(--ds-motion-standard) var(--ds-ease-out), + color var(--ds-motion-fast) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; } html[data-dream-skin="active"] aside.app-shell-left-panel [class*="text-token-foreground"] { @@ -251,8 +497,9 @@ html[data-dream-skin="active"] aside.app-shell-left-panel svg { } html[data-dream-skin="active"] aside.app-shell-left-panel button:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel a:hover { - background: rgb(var(--ds-accent-rgb) / .09) !important; +html[data-dream-skin="active"] aside.app-shell-left-panel a:hover, +html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:hover { + background: var(--ds-hover) !important; color: var(--ds-text) !important; transform: none !important; } @@ -279,9 +526,23 @@ html[data-dream-skin="active"] aside.app-shell-left-panel button[aria-label^=" html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] { - background: rgb(var(--ds-accent-rgb) / .12) !important; - border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; - box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; + background: var(--ds-selected) !important; + border: 1px solid var(--ds-accent) !important; + border-radius: 9px 9px 10px 10px !important; + box-shadow: + 0 4px 14px rgb(var(--ds-bg-rgb) / .14), + inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; + color: var(--ds-text) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:active, +html[data-dream-skin="active"] aside.app-shell-left-panel button:active, +html[data-dream-skin="active"] aside.app-shell-left-panel a:active { + background: var(--ds-pressed) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] [class*="text-token"], +html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"] [class*="text-token"] { color: var(--ds-text) !important; } @@ -289,6 +550,18 @@ html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] color: var(--ds-accent) !important; } +/* Onboarding and task suggestion cards are native sidebar surfaces, not + dialogs. When Codex itself is dark they otherwise remain charcoal beneath a + light skin. Keep them opaque enough for copy while preserving a soft lift. */ +html[data-dream-skin="active"][data-dream-shell="light"] aside.app-shell-left-panel + [class~="bg-token-main-surface-primary/70"] { + background: var(--ds-selected) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-surface) !important; + color: var(--ds-text) !important; + backdrop-filter: blur(14px) saturate(104%) !important; +} + html[data-dream-skin="active"] main.main-surface { position: relative !important; isolation: isolate; @@ -411,10 +684,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -425,21 +699,29 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } +/* The artwork is painted once on the fixed body layer. Do not create a second + right-side image window: its own width, mask and vertical bounds form a + visible page seam and can bisect the sticky composer region. */ +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"]))::after { + content: none !important; + display: none !important; +} + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(14px) saturate(102%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface > header.app-header-tint { - background: transparent !important; - border-bottom: 0 !important; + background: var(--composer-surface) !important; + border: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -454,9 +736,7 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] main.main-surface:not(:has([role="main"])) [class*="_markdown"] { - text-shadow: - 0 1px 2px rgb(var(--ds-panel-rgb) / .92), - 0 0 10px rgb(var(--ds-panel-rgb) / .72); + text-shadow: none; } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -493,6 +773,26 @@ html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) [role= 0 0 10px rgb(var(--ds-bg-rgb) / .46); } +/* Non-immersive task routes keep a conservative reading surface. Wide ambient + routes clear this local layer below because their veil already lives on the + complete main canvas, including the composer zone. */ +html[data-dream-skin="active"] + main.main-surface:not(:has([role="main"])) + .thread-scroll-container { + background: + linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + var(--ds-content-reading-plane-far) 100%) !important; + backdrop-filter: none !important; +} + +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"])) + .thread-scroll-container { + background: transparent !important; +} + html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) [role="main"] { text-shadow: none; } @@ -510,19 +810,19 @@ html[data-dream-skin="active"] main.main-surface html[data-dream-skin="active"] main.main-surface div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: rgb(var(--ds-panel-rgb) / .62) !important; + background: var(--ds-content-reading-plane) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: rgb(var(--ds-panel-rgb) / .44); + background: var(--message-surface); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(7px) saturate(105%); + backdrop-filter: blur(14px) saturate(108%); } html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) article { - background: rgb(var(--ds-panel-rgb) / .72); + background: var(--message-surface); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -532,70 +832,25 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets. A solid surface + is enough; a theme border/shadow becomes a full-width false divider. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity comes from the artwork and palette, so task chrome carries no + absolute-positioned labels and the home composer has no floating quote. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +1144,81 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-border) !important; + border-width: 1px !important; + border-style: solid !important; + border-color: var(--ds-border) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; - box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + background: var(--composer-surface) !important; + box-shadow: var(--ds-shadow-surface) !important; + backdrop-filter: blur(20px) saturate(108%) !important; + transition: + border-color var(--ds-motion-standard) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out), + background-color var(--ds-motion-standard) var(--ds-ease-out) !important; +} + +/* Current Codex builds force the native composer border width to zero from a + higher-priority cascade layer. Draw the semantic 1px boundary separately so + it remains stable without changing content opacity or input geometry. */ +html[data-dream-skin="active"] .composer-surface-chrome::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-border) !important; + border-radius: inherit !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:hover { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:hover::before { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:focus-within { + border-color: var(--ds-focus) !important; + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; + box-shadow: var(--ds-shadow-elevated) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:focus-within::before { + border-color: var(--ds-focus) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled), +html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"] { + color: var(--ds-text-muted) !important; + background: var(--ds-input-disabled) !important; + border-color: var(--ds-border) !important; + box-shadow: none !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled)::before, +html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"]::before { + border-color: var(--ds-border) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"]) { + border-color: var(--ds-danger) !important; + outline-color: var(--ds-danger) !important; } -html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } +html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"])::before { + border-color: var(--ds-danger) !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +1301,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -995,12 +1315,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] .composer-surface-chrome p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .78) !important; + color: var(--ds-text-secondary) !important; opacity: 1 !important; } @@ -1013,7 +1333,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1023,7 +1343,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"]) .composer-surface-chrome { - border: 0 !important; + border: 1px solid var(--ds-border) !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1033,12 +1353,12 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has( } html[data-dream-skin="active"][data-dream-art-wide="true"] [class*="_homeUtilityBar_"] { - background: var(--ds-immersive-composer-solid) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1056,7 +1376,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"] [class*="_homeUtilityBar_"] button, @@ -1081,7 +1401,55 @@ html[data-dream-skin="active"] .composer-surface-chrome button:not([class~="bg-t } html[data-dream-skin="active"] .composer-surface-chrome p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .82) !important; + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +html[data-dream-skin="active"] :is(input, textarea)::placeholder { + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +/* Menus, popovers and dialogs are independent elevated surfaces. Background + content may inform their material tint but must never remain legible through + them. */ +html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), +html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { + color: var(--ds-text-primary) !important; + background: var(--ds-popover) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-elevated) !important; + backdrop-filter: blur(18px) saturate(102%) !important; +} + +html[data-dream-skin="active"] :is( + button, + a, + input, + textarea, + [role="button"], + [role="menuitem"], + [tabindex] +):focus-visible { + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; +} + +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +):disabled, +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +)[aria-disabled="true"] { + color: var(--ds-text-muted) !important; opacity: 1 !important; } @@ -1173,12 +1541,26 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { + background: var(--ds-highlight) !important; +} + +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { + transform: translateY(1px); + box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; +} + html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { + html[data-dream-skin="active"] { + --ambient-start: 52%; + } html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { content: none; } + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"]))::after { content: none; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/macos/assets/renderer-inject.js b/macos/assets/renderer-inject.js index be799888..9ef10592 100644 --- a/macos/assets/renderer-inject.js +++ b/macos/assets/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Keep the old property names in + // THEME_VARIABLES so reinjection removes values left by earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/macos/assets/selectors.json b/macos/assets/selectors.json index 06e22231..ea0f0994 100644 --- a/macos/assets/selectors.json +++ b/macos/assets/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/macos/package.json b/macos/package.json index f8ebed43..b992b814 100644 --- a/macos/package.json +++ b/macos/package.json @@ -1,6 +1,6 @@ { "name": "codex-dream-skin-studio", - "version": "1.5.5", + "version": "1.5.6", "private": true, "type": "module", "scripts": { diff --git a/macos/scripts/common-macos.sh b/macos/scripts/common-macos.sh index 32a58da2..921ae633 100755 --- a/macos/scripts/common-macos.sh +++ b/macos/scripts/common-macos.sh @@ -29,7 +29,7 @@ CODEX_APP_JOB_LABEL="com.openai.codex-dream-skin-studio.app" INJECTOR_JOB_LABEL="com.openai.codex-dream-skin-studio.injector" EXPECTED_CODEX_TEAM_ID="2DC432GLL2" EXPECTED_CODEX_REQUIREMENT="anchor apple generic and certificate leaf[subject.OU] = \"$EXPECTED_CODEX_TEAM_ID\"" -SKIN_VERSION="1.5.5" +SKIN_VERSION="1.5.6" DREAM_SKIN_VALIDATED_RUNTIME_PID="" DREAM_SKIN_VALIDATED_RUNTIME_BUNDLE="" DREAM_SKIN_VALIDATED_RUNTIME_EXE="" diff --git a/macos/scripts/injector.mjs b/macos/scripts/injector.mjs index 924fe814..871420b1 100644 --- a/macos/scripts/injector.mjs +++ b/macos/scripts/injector.mjs @@ -42,7 +42,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.5"; +const SKIN_VERSION = "1.5.6"; const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]); const CDP_ID_PATTERN = /^[A-Za-z0-9._-]{1,200}$/; const MAX_ART_BYTES = 10 * 1024 * 1024; @@ -232,13 +232,18 @@ export function assessRendererVerification(renderer, nativeWindow, expected) { const structurePass = settingsRoute ? Boolean(result.settings?.visible) : Boolean(result.shell?.visible) && Boolean(result.sidebar?.visible); + const threadScrollPass = !result.threadSurface || ( + result.threadSurface.visible + && ["auto", "scroll", "overlay"].includes(result.threadSurface.overflowY) + && result.threadSurface.pointerEvents !== "none" + ); const nativeWindowPass = nativeWindow?.status === "ready"; const fallbackWindowPass = nativeWindow?.status === "unsupported"; const windowPass = documentVisible && viewportPass && (nativeWindowPass || fallbackWindowPass); const basePass = result.installed && result.version === expected.skinVersion && result.stylePresent && result.businessClassPollution === 0 - && structurePass && windowPass && !result.documentOverflow?.x; + && structurePass && threadScrollPass && windowPass && !result.documentOverflow?.x; const payloadPass = (!expected.expectedThemeId || result.themeId === expected.expectedThemeId) && (!expected.expectedRevision || result.revision === expected.expectedRevision); const visibleSuggestionLabels = Array.isArray(result.suggestionLabels) @@ -258,6 +263,7 @@ export function assessRendererVerification(renderer, nativeWindow, expected) { nativeWindowPass, payloadPass, structurePass, + threadScrollPass, viewportPass, windowPass, }; @@ -1103,6 +1109,14 @@ async function verifySession(session, expectedThemeId = null, expectedRevision = const shell = box(document.querySelector(${selectorLiteral("shell-main")})); const composer = box(document.querySelector(${selectorLiteral("composer-chrome")})); const sidebar = box(document.querySelector(${selectorLiteral("left-panel")})); + const threadSurfaceNode = document.querySelector(${selectorLiteral("thread-surface")}); + const threadSurfaceStyle = threadSurfaceNode ? getComputedStyle(threadSurfaceNode) : null; + const threadSurface = threadSurfaceNode ? { + ...box(threadSurfaceNode), + overflowX: threadSurfaceStyle.overflowX, + overflowY: threadSurfaceStyle.overflowY, + pointerEvents: threadSurfaceStyle.pointerEvents, + } : null; const settingsBoxes = [ box(document.querySelector(${selectorLiteral("appearance-radio")})), box(document.querySelector(${stableTestidLiteral("theme-preview")})), @@ -1137,6 +1151,7 @@ async function verifySession(session, expectedThemeId = null, expectedRevision = shell, composer, sidebar, + threadSurface, settings, viewport: { width: innerWidth, height: innerHeight }, documentOverflow: { diff --git a/macos/tests/run-tests.sh b/macos/tests/run-tests.sh index ca068e6d..bfee73ff 100755 --- a/macos/tests/run-tests.sh +++ b/macos/tests/run-tests.sh @@ -82,7 +82,7 @@ UPDATE_JSON="$({ })" "$NODE" -e ' const value = JSON.parse(process.argv[1]); - if (value.currentVersion !== "v1.5.5" || value.latestVersion !== "v9.8.7") process.exit(1); + if (value.currentVersion !== "v1.5.6" || value.latestVersion !== "v9.8.7") process.exit(1); if (!value.updateAvailable) process.exit(1); if (value.releaseUrl !== "https://github.com/Fei-Away/Codex-Dream-Skin/releases/latest") process.exit(1); ' "$UPDATE_JSON" @@ -1063,7 +1063,7 @@ CRLF_BACKUP="$TMP/config-crlf-backup.json" "$NODE" "$ROOT/scripts/theme-config.mjs" restore "$CRLF_CONFIG" "$CRLF_BACKUP" >/dev/null /usr/bin/cmp -s "$CRLF_CONFIG" "$TMP/original-crlf.toml" -/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.5" ]' _ "$ROOT" +/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.6" ]' _ "$ROOT" if [ "${CODEX_DREAM_SKIN_SKIP_DOCTOR:-0}" = "1" ]; then printf 'SKIP: Doctor requires an installed, signed Codex app.\n' DOCTOR_RESULT="skipped" diff --git a/macos/tests/window-readiness.test.mjs b/macos/tests/window-readiness.test.mjs index bc11a6d1..c97de454 100644 --- a/macos/tests/window-readiness.test.mjs +++ b/macos/tests/window-readiness.test.mjs @@ -42,6 +42,14 @@ const baseRenderer = { scope: { level: "L1", baseState: "thread" }, shell: { visible: true, width: 900, height: 740 }, sidebar: { visible: true, width: 280, height: 740 }, + threadSurface: { + visible: true, + width: 900, + height: 700, + overflowX: "hidden", + overflowY: "auto", + pointerEvents: "auto", + }, settings: null, homeRoute: false, homePresent: false, @@ -55,6 +63,20 @@ const baseRenderer = { assert.equal(readyNativeWindow.status, "ready"); assert.equal(assessRendererVerification(baseRenderer, readyNativeWindow, exactPayload).pass, true); +const brokenThreadScroll = { + ...baseRenderer, + threadSurface: { ...baseRenderer.threadSurface, overflowY: "visible" }, +}; +assert.equal( + assessRendererVerification(brokenThreadScroll, readyNativeWindow, exactPayload).pass, + false, + "A skin that replaces the native task scroller with visible overflow must fail verification.", +); +assert.equal( + assessRendererVerification(brokenThreadScroll, readyNativeWindow, exactPayload) + .checks.threadScrollPass, + false, +); const windowCalls = []; assert.equal((await inspectNativeWindow({ diff --git a/runtime/dream-skin.css b/runtime/dream-skin.css index b2559ffc..5b865196 100644 --- a/runtime/dream-skin.css +++ b/runtime/dream-skin.css @@ -59,6 +59,70 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* One-canvas layout contract. Native window chrome and content geometry + remain authoritative; artwork must adapt to the product, never reserve + layout space at the expense of typing or reading comfort. */ + --titlebar-safe-left: max(env(safe-area-inset-left), 0px); + --titlebar-safe-right: max(env(safe-area-inset-right), 0px); + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + /* Semantic design tokens. Theme packages provide the primitive palette; + native components consume these roles so material, state and accessibility + decisions stay consistent instead of scattering alpha values. */ + --ds-canvas: var(--ds-bg); + --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); + --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); + --reading-veil: linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); + --ds-surface: var(--ds-panel); + --message-surface: rgb(var(--ds-panel-rgb) / .94); + --ds-elevated-surface: var(--ds-panel); + --ds-popover: rgb(var(--ds-panel-rgb) / .988); + --ds-input: rgb(var(--ds-panel-rgb) / .985); + --composer-surface: rgb(var(--ds-panel-rgb) / .92); + --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); + --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); + --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); + --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); + --ds-focus: var(--ds-accent); + --ds-border: var(--ds-line); + --ds-text-primary: var(--ds-text); + --ds-text-secondary: var(--ds-muted); + --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); + --ds-danger: #ff9a8f; + --ds-danger-surface: #4b1f22; + --ds-danger-foreground: #fff0ed; + --ds-danger-border: #d87970; + --ds-success: #80c79b; + --ds-success-surface: #203d2d; + --ds-success-foreground: #e6f4e9; + --ds-success-border: #72a782; + --ds-warning-surface: #4c351c; + --ds-warning-foreground: #fff0cf; + --ds-warning-border: #cf984b; + --ds-info-surface: #223842; + --ds-info-foreground: #edf7fa; + --ds-info-border: #7899a6; + --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); + --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); + --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); + --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); + --ds-zone-shadow: + 0 7px 18px rgb(var(--ds-bg-rgb) / .09), + inset 0 1px rgb(var(--ds-panel-rgb) / .82); + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); + --ds-shadow-elevated: + 0 16px 38px rgb(var(--ds-bg-rgb) / .20), + 0 2px 8px rgb(var(--ds-bg-rgb) / .12); + --ds-motion-fast: 120ms; + --ds-motion-standard: 160ms; + --ds-motion-reveal: 210ms; + --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -107,6 +171,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); + --ds-danger: #9c3d3a; + --ds-danger-surface: #f8e6e3; + --ds-danger-foreground: #6f2523; + --ds-danger-border: #b85650; + --ds-success: #45624d; + --ds-success-surface: #e4efe5; + --ds-success-foreground: #28543a; + --ds-success-border: #5f856b; + --ds-warning-surface: #f8edd7; + --ds-warning-foreground: #674515; + --ds-warning-border: #aa7a32; + --ds-info-surface: #e8eef1; + --ds-info-foreground: #284754; + --ds-info-border: #6f8b97; + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); + --ds-shadow-elevated: + 0 18px 42px rgb(var(--ds-bg-rgb) / .16), + 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -115,6 +197,131 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); + --vscode-inputValidation-errorBackground: var(--ds-danger-surface); + --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); + --vscode-inputValidation-errorBorder: var(--ds-danger-border); + --color-token-input-validation-error-background: var(--ds-danger-surface); + --color-token-input-validation-error-foreground: var(--ds-danger-foreground); + --color-token-input-validation-error-border: var(--ds-danger-border); + --color-token-error-foreground: var(--ds-danger-foreground); + --color-token-editor-error-foreground: var(--ds-danger-foreground); +} + +/* Codex can remain electron-dark while a theme explicitly requests a light + shell. Bridge the native token family to the active skin so task cards, + inputs, secondary copy and icons do not keep dark-mode values on a light + wallpaper. This is component-scoped token remapping, never whole-page + opacity. */ +html[data-dream-skin="active"][data-dream-shell="light"] { + --vscode-foreground: var(--ds-text); + --vscode-descriptionForeground: var(--ds-muted); + --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); + --color-background-panel: var(--ds-panel); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); + --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); + --color-token-conversation-body: var(--ds-muted); + --color-token-conversation-summary-leading: var(--ds-muted); + --color-token-conversation-summary-trailing: var(--ds-muted); + --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); + --color-token-description-foreground: var(--ds-muted); + --color-token-list-hover-background: var(--ds-hover); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* The scheduled-task detail side panel uses a separate inline panel surface + variable and input token family. Reinforce the light-shell bridge at its + native form boundary so the prompt, detail and schedule cards cannot retain + electron-dark surfaces or pale input copy. */ +html[data-dream-skin="active"][data-dream-shell="light"] + [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { + color: var(--ds-text); + --color-background-panel: var(--ds-input); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: var(--ds-muted); + --color-token-description-foreground: var(--ds-muted); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* Native toasts use one generic foreground token across four different + surfaces. Resolve the actual host state classes into independent semantic + palettes before styling the shared alert component. */ +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-dropdown-background"] { + --ds-alert-surface: var(--ds-info-surface); + --ds-alert-foreground: var(--ds-info-foreground); + --ds-alert-border: var(--ds-info-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { + --ds-alert-surface: var(--ds-success-surface); + --ds-alert-foreground: var(--ds-success-foreground); + --ds-alert-border: var(--ds-success-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { + --ds-alert-surface: var(--ds-warning-surface); + --ds-alert-foreground: var(--ds-warning-foreground); + --ds-alert-border: var(--ds-warning-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { + --ds-alert-surface: var(--ds-danger-surface); + --ds-alert-foreground: var(--ds-danger-foreground); + --ds-alert-border: var(--ds-danger-border); +} + +html[data-dream-skin="active"] [role="alert"].alert-root { + background-color: var(--ds-alert-surface, var(--ds-popover)) !important; + border-color: var(--ds-alert-border, var(--ds-border)) !important; + color: var(--ds-alert-foreground, var(--ds-text)) !important; + --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { + color: var(--ds-alert-foreground, var(--ds-text)) !important; + opacity: .76 !important; +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { + opacity: 1 !important; +} + +/* Active tool/agent summaries render duplicated shimmer text with hard-coded + alpha and a white sweep. Keep the base label readable and tint only its + moving overlay with the skin accent; no ancestor opacity is changed. */ +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { + color: var(--ds-muted) !important; + -webkit-text-fill-color: var(--ds-muted) !important; +} + +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { + color: var(--ds-accent) !important; + -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -130,12 +337,10 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; - --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; - --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -218,9 +423,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ { background: linear-gradient(180deg, - rgb(var(--ds-panel-rgb) / .98), - rgb(var(--ds-bg-rgb) / .96)) !important; - border: 1px solid var(--ds-line) !important; + var(--ds-sidebar), + rgb(var(--ds-bg-rgb) / .975)) !important; + border: 1px solid var(--ds-border) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -230,12 +435,53 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ { html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ nav { background: transparent !important; } +/* Recent and other semantic lists may read as quiet raised sections. The + primary navigation stays on the native flat canvas: wrapping its split + fixed/scrolling DOM in a faux card creates the broken right edge called out + in the screenshot and relies on brittle nth-child geometry. */ +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ + nav section:has([role="list"]) { + box-sizing: border-box !important; + margin-inline: 8px !important; + padding: 7px 6px 8px !important; + border: 1px solid var(--ds-zone-border) !important; + border-radius: 14px !important; + background: var(--ds-zone-surface-raised) !important; + box-shadow: var(--ds-zone-shadow) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ + nav section:has([role="list"]) > div > div:first-child { + margin-bottom: 3px !important; + padding-bottom: 5px !important; + border-bottom: 1px solid var(--ds-zone-divider) !important; +} + +/* Use the existing 16px native resize target as the visual boundary. The line + is non-interactive and never narrows or covers the resizer hit area. */ +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ > [role="separator"]::before { + content: "" !important; + position: absolute !important; + top: 0 !important; + bottom: 0 !important; + left: 8px !important; + width: 1px !important; + background: var(--ds-zone-border) !important; + box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ > [role="separator"]:hover::before { + background: rgb(var(--ds-accent-rgb) / .52) !important; +} + html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button, html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a { color: var(--ds-text) !important; - transition: background-color .18s cubic-bezier(.22, 1, .36, 1), - border-color .18s cubic-bezier(.22, 1, .36, 1), - color .18s cubic-bezier(.22, 1, .36, 1) !important; + transition: background-color var(--ds-motion-standard) var(--ds-ease-out), + border-color var(--ds-motion-standard) var(--ds-ease-out), + color var(--ds-motion-fast) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class*="text-token-foreground"] { @@ -251,8 +497,9 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ svg { } html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button:hover, -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:hover { - background: rgb(var(--ds-accent-rgb) / .09) !important; +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:hover, +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [role="button"]:hover { + background: var(--ds-hover) !important; color: var(--ds-text) !important; transform: none !important; } @@ -279,9 +526,23 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button[aria-label^= html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page"] { - background: rgb(var(--ds-accent-rgb) / .12) !important; - border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; - box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; + background: var(--ds-selected) !important; + border: 1px solid var(--ds-accent) !important; + border-radius: 9px 9px 10px 10px !important; + box-shadow: + 0 4px 14px rgb(var(--ds-bg-rgb) / .14), + inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; + color: var(--ds-text) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [role="button"]:active, +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button:active, +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:active { + background: var(--ds-pressed) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page"] [class*="text-token"], +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class~="bg-token-list-hover-background"] [class*="text-token"] { color: var(--ds-text) !important; } @@ -289,6 +550,18 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page color: var(--ds-accent) !important; } +/* Onboarding and task suggestion cards are native sidebar surfaces, not + dialogs. When Codex itself is dark they otherwise remain charcoal beneath a + light skin. Keep them opaque enough for copy while preserving a soft lift. */ +html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_LEFT_PANEL__ + [class~="bg-token-main-surface-primary/70"] { + background: var(--ds-selected) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-surface) !important; + color: var(--ds-text) !important; + backdrop-filter: blur(14px) saturate(104%) !important; +} + html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ { position: relative !important; isolation: isolate; @@ -411,10 +684,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -425,21 +699,29 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } +/* The artwork is painted once on the fixed body layer. Do not create a second + right-side image window: its own width, mask and vertical bounds form a + visible page seam and can bisect the sticky composer region. */ +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__))::after { + content: none !important; + display: none !important; +} + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(14px) saturate(102%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_SHELL_MAIN__ > __DREAM_SELECTOR_HEADER_TINT__ { - background: transparent !important; - border-bottom: 0 !important; + background: var(--composer-surface) !important; + border: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -454,9 +736,7 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_MARKDOWN__ { - text-shadow: - 0 1px 2px rgb(var(--ds-panel-rgb) / .92), - 0 0 10px rgb(var(--ds-panel-rgb) / .72); + text-shadow: none; } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -493,6 +773,26 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SE 0 0 10px rgb(var(--ds-bg-rgb) / .46); } +/* Non-immersive task routes keep a conservative reading surface. Wide ambient + routes clear this local layer below because their veil already lives on the + complete main canvas, including the composer zone. */ +html[data-dream-skin="active"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) + .thread-scroll-container { + background: + linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + var(--ds-content-reading-plane-far) 100%) !important; + backdrop-filter: none !important; +} + +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) + .thread-scroll-container { + background: transparent !important; +} + html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) [role="main"] { text-shadow: none; } @@ -510,19 +810,19 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: rgb(var(--ds-panel-rgb) / .62) !important; + background: var(--ds-content-reading-plane) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: rgb(var(--ds-panel-rgb) / .44); + background: var(--message-surface); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(7px) saturate(105%); + backdrop-filter: blur(14px) saturate(108%); } html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) article { - background: rgb(var(--ds-panel-rgb) / .72); + background: var(--message-surface); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -532,70 +832,25 @@ html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_HOME_R } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ > __DREAM_SELECTOR_HEADER_TINT__ { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets. A solid surface + is enough; a theme border/shadow becomes a full-width false divider. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity comes from the artwork and palette, so task chrome carries no + absolute-positioned labels and the home composer has no floating quote. */ html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after, html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, -html[data-dream-skin="active"][data-dream-shell="light"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +1144,81 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HO html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HOME_SUGGESTIONS__ button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-border) !important; + border-width: 1px !important; + border-style: solid !important; + border-color: var(--ds-border) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; - box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + background: var(--composer-surface) !important; + box-shadow: var(--ds-shadow-surface) !important; + backdrop-filter: blur(20px) saturate(108%) !important; + transition: + border-color var(--ds-motion-standard) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out), + background-color var(--ds-motion-standard) var(--ds-ease-out) !important; +} + +/* Current Codex builds force the native composer border width to zero from a + higher-priority cascade layer. Draw the semantic 1px boundary separately so + it remains stable without changing content opacity or input geometry. */ +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-border) !important; + border-radius: inherit !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:hover { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:hover::before { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:focus-within { + border-color: var(--ds-focus) !important; + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; + box-shadow: var(--ds-shadow-elevated) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:focus-within::before { + border-color: var(--ds-focus) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has(:disabled), +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__[aria-disabled="true"] { + color: var(--ds-text-muted) !important; + background: var(--ds-input-disabled) !important; + border-color: var(--ds-border) !important; + box-shadow: none !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has(:disabled)::before, +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__[aria-disabled="true"]::before { + border-color: var(--ds-border) !important; +} + +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has([aria-invalid="true"]) { + border-color: var(--ds-danger) !important; + outline-color: var(--ds-danger) !important; } -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { content: none !important; } +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has([aria-invalid="true"])::before { + border-color: var(--ds-danger) !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +1301,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_SHEL } html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -995,12 +1315,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_COMPOSER_CHROME__ p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .78) !important; + color: var(--ds-text-secondary) !important; opacity: 1 !important; } @@ -1013,7 +1333,7 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1023,7 +1343,7 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ { html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__:has(__DREAM_SELECTOR_HOME_UTILITY__) __DREAM_SELECTOR_COMPOSER_CHROME__ { - border: 0 !important; + border: 1px solid var(--ds-border) !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1033,12 +1353,12 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__:has(__DREAM_SELECTO } html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_HOME_UTILITY__ { - background: var(--ds-immersive-composer-solid) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1056,7 +1376,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ button, @@ -1081,7 +1401,55 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ button:not([cl } html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .82) !important; + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +html[data-dream-skin="active"] :is(input, textarea)::placeholder { + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +/* Menus, popovers and dialogs are independent elevated surfaces. Background + content may inform their material tint but must never remain legible through + them. */ +html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), +html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { + color: var(--ds-text-primary) !important; + background: var(--ds-popover) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-elevated) !important; + backdrop-filter: blur(18px) saturate(102%) !important; +} + +html[data-dream-skin="active"] :is( + button, + a, + input, + textarea, + [role="button"], + [role="menuitem"], + [tabindex] +):focus-visible { + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; +} + +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +):disabled, +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +)[aria-disabled="true"] { + color: var(--ds-text-muted) !important; opacity: 1 !important; } @@ -1173,12 +1541,26 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { + background: var(--ds-highlight) !important; +} + +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { + transform: translateY(1px); + box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; +} + html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { + html[data-dream-skin="active"] { + --ambient-start: 52%; + } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)::after { content: none; } + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__))::after { content: none; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/runtime/renderer-inject.js b/runtime/renderer-inject.js index 4066c711..49563f24 100644 --- a/runtime/renderer-inject.js +++ b/runtime/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Keep the old property names in + // THEME_VARIABLES so reinjection removes values left by earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/tools/renderer-runtime.test.mjs b/tools/renderer-runtime.test.mjs index c0492567..6676d8a5 100644 --- a/tools/renderer-runtime.test.mjs +++ b/tools/renderer-runtime.test.mjs @@ -3,6 +3,25 @@ import fs from "node:fs/promises"; import path from "node:path"; import vm from "node:vm"; +function contrastRatio(firstHex, secondHex) { + const luminance = (hex) => { + const channels = hex.match(/[0-9a-f]{2}/gi).map((value) => Number.parseInt(value, 16) / 255); + const linear = channels.map((value) => ( + value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4 + )); + return (0.2126 * linear[0]) + (0.7152 * linear[1]) + (0.0722 * linear[2]); + }; + const first = luminance(firstHex); + const second = luminance(secondHex); + return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05); +} + +function hexVariable(block, name) { + const match = block.match(new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, "i")); + assert.ok(match, `Expected ${name} to be a six-digit hex color.`); + return match[1]; +} + function styleDeclaration() { const values = new Map(); return { @@ -236,6 +255,26 @@ export async function runRendererRuntimeTest(assetRoot) { const template = await fs.readFile(path.join(assetRoot, "renderer-inject.js"), "utf8"); const css = await fs.readFile(path.join(assetRoot, "dream-skin.css"), "utf8"); fixture.template = template; + const darkThemeBlock = css.match(/:root\[data-dream-skin="active"\]\s*\{([\s\S]*?)\n\}/)?.[1]; + const lightThemeBlock = css.match(/html\[data-dream-skin="active"\]\[data-dream-shell="light"\]\s*\{([\s\S]*?)\n\}/)?.[1]; + assert.ok(darkThemeBlock, "Expected the active dark-shell semantic token block."); + assert.ok(lightThemeBlock, "Expected the active light-shell semantic token block."); + for (const role of ["danger", "success", "warning", "info"]) { + assert.ok( + contrastRatio( + hexVariable(darkThemeBlock, `--ds-${role}-foreground`), + hexVariable(darkThemeBlock, `--ds-${role}-surface`), + ) >= 4.5, + `Dark-shell ${role} alerts must meet WCAG AA text contrast.`, + ); + assert.ok( + contrastRatio( + hexVariable(lightThemeBlock, `--ds-${role}-foreground`), + hexVariable(lightThemeBlock, `--ds-${role}-surface`), + ) >= 4.5, + `Light-shell ${role} alerts must meet WCAG AA text contrast.`, + ); + } assert.match(template, /adoptedStyleSheets/); assert.match(template, /CSSStyleSheet/); @@ -258,12 +297,108 @@ export async function runRendererRuntimeTest(assetRoot) { assert.match(css, /main\.main-surface:has\(\[role="main"\]\)/); assert.match(css, /main\.main-surface:not\(:has\(\[role="main"\]\)\)/); assert.doesNotMatch(css, /:has\([^()]*:has\(/); - assert.match(css, /content:\s*var\(--dream-skin-name[\s\S]{0,180}var\(--dream-skin-brand-subtitle/); - assert.match(css, /content:\s*var\(--dream-skin-status/); - assert.match(css, /content:\s*var\(--dream-skin-quote/); + assert.doesNotMatch(css, + /\[aria-label="(?:隐藏边栏|Hide sidebar|返回|Back|前进|Forward)"\]/, + "Theme CSS must not resize or reposition native titlebar controls."); + assert.match(css, + /header\.app-header-tint::before,[\s\S]*?header\.app-header-tint::after,[\s\S]*?main\.main-surface:has\(\[role="main"\]\)::after\s*\{[\s\S]*?content:\s*none\s*!important[\s\S]*?display:\s*none\s*!important/, + "Task chrome and home composer areas must not carry positioned theme slogans."); + assert.doesNotMatch(css, + /content:\s*var\(--dream-skin-(?:chrome-mark|status|quote)/, + "Retired theme slogans must not be painted into native chrome."); assert.match(css, /--ds-task-full-veil/); assert.match(css, /data-dream-task-mode="full"/); assert.match(css, /background-image:\s*var\(--ds-task-full-veil\),\s*var\(--dream-skin-art\)/); + assert.match(css, + /data-dream-shell="light"\]\s*\{[\s\S]*?--vscode-foreground:\s*var\(--ds-text\)[\s\S]*?--color-background-panel:\s*var\(--ds-panel\)[\s\S]*?--color-token-bg-fog:\s*var\(--ds-panel-2\)[\s\S]*?--color-token-main-surface-primary:\s*var\(--ds-panel\)[\s\S]*?--color-token-conversation-body:\s*var\(--ds-muted\)[\s\S]*?--color-token-conversation-summary-trailing:\s*var\(--ds-muted\)[\s\S]*?--color-token-input-background:\s*var\(--ds-input\)[\s\S]*?--color-token-input-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-placeholder-foreground:\s*var\(--ds-muted\)[\s\S]*?--color-token-border:\s*var\(--ds-border\)/, + "A light skin must bridge native dark-shell text, panel, task-card, conversation, input and border tokens."); + assert.match(css, + /\[class~="bg-token-main-surface-primary"\]:has\(form\[id\^="automation-side-panel-form"\]\)\s*\{[\s\S]*?--color-background-panel:\s*var\(--ds-input\)[\s\S]*?--color-token-main-surface-primary:\s*var\(--ds-panel\)[\s\S]*?--color-token-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-placeholder-foreground:\s*var\(--ds-muted\)[\s\S]*?--color-token-border:\s*var\(--ds-border\)/, + "Scheduled-task side panels must not retain electron-dark cards or pale input text under a light skin."); + assert.match(css, + /\.loading-shimmer-pure-text\s*\{[\s\S]*?color:\s*var\(--ds-muted\)\s*!important[\s\S]*?-webkit-text-fill-color:\s*var\(--ds-muted\)\s*!important[\s\S]*?\.loading-shimmer-pure-text\s+\*\s*\{[\s\S]*?color:\s*var\(--ds-accent\)\s*!important[\s\S]*?-webkit-text-fill-color:\s*var\(--ds-accent\)\s*!important/, + "Active conversation summaries must keep readable theme-colored shimmer text."); + assert.match(css, + /\[aria-current="page"\]\s*\{[\s\S]*?background:\s*var\(--ds-selected\)[\s\S]*?border:\s*1px solid var\(--ds-accent\)[\s\S]*?box-shadow:[\s\S]*?inset 3px 0 rgb\(var\(--ds-accent-rgb\)/, + "Selected sidebar rows must use a readable surface plus a solid accent indicator."); + assert.match(css, + /\[class~="bg-token-main-surface-primary\/70"\]\s*\{[\s\S]*?background:\s*var\(--ds-selected\)[\s\S]*?color:\s*var\(--ds-text\)/, + "Native sidebar task cards must not retain the host dark surface under a light skin."); + assert.doesNotMatch(css, /aside\.app-shell-left-panel\s+nav\s*>\s*div(?::first-child|:nth-child)/, + "Primary navigation must keep native flow instead of brittle split-card geometry."); + assert.match(css, + /aside\.app-shell-left-panel[\s\S]*?nav section:has\(\[role="list"\]\)\s*\{[\s\S]*?margin-inline:\s*8px\s*!important[\s\S]*?border:\s*1px solid var\(--ds-zone-border\)[\s\S]*?background:\s*var\(--ds-zone-surface-raised\)/, + "Semantic sidebar lists must own distinct raised section surfaces."); + assert.match(css, + /aside\.app-shell-left-panel > \[role="separator"\]::before\s*\{[\s\S]*?left:\s*8px\s*!important[\s\S]*?width:\s*1px\s*!important[\s\S]*?pointer-events:\s*none\s*!important/, + "The native resize target must expose a clear non-interactive visual boundary."); + assert.match(css, + /--titlebar-safe-left:[\s\S]*?--titlebar-safe-right:[\s\S]*?--ambient-start:\s*52%[\s\S]*?--ambient-feather:\s*clamp\(240px,\s*24vw,\s*320px\)[\s\S]*?--ds-canvas:\s*var\(--ds-bg\)[\s\S]*?--ds-content-reading-plane:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.975\)[\s\S]*?--reading-veil:\s*linear-gradient\([\s\S]*?--message-surface:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.94\)[\s\S]*?--ds-popover:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.988\)[\s\S]*?--ds-input:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.985\)[\s\S]*?--composer-surface:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.92\)[\s\S]*?--ds-selected:[\s\S]*?--ds-hover:[\s\S]*?--ds-pressed:[\s\S]*?--ds-focus:[\s\S]*?--ds-danger:[\s\S]*?--ds-danger-surface:[\s\S]*?--ds-danger-foreground:[\s\S]*?--ds-danger-border:[\s\S]*?--ds-success:[\s\S]*?--ds-success-surface:[\s\S]*?--ds-success-foreground:[\s\S]*?--ds-success-border:[\s\S]*?--ds-warning-surface:[\s\S]*?--ds-warning-foreground:[\s\S]*?--ds-warning-border:[\s\S]*?--ds-info-surface:[\s\S]*?--ds-info-foreground:[\s\S]*?--ds-info-border:[\s\S]*?--ds-zone-surface:[\s\S]*?--ds-zone-surface-raised:[\s\S]*?--ds-zone-border:[\s\S]*?--ds-zone-divider:/, + "The renderer must expose a complete semantic token layer for surfaces, states and status colors."); + assert.match(css, + /--vscode-inputValidation-errorBackground:\s*var\(--ds-danger-surface\)[\s\S]*?--vscode-inputValidation-errorForeground:\s*var\(--ds-danger-foreground\)[\s\S]*?--vscode-inputValidation-errorBorder:\s*var\(--ds-danger-border\)[\s\S]*?--color-token-input-validation-error-background:\s*var\(--ds-danger-surface\)[\s\S]*?--color-token-input-validation-error-foreground:\s*var\(--ds-danger-foreground\)[\s\S]*?--color-token-input-validation-error-border:\s*var\(--ds-danger-border\)/, + "Native validation tokens must resolve to the skin's semantic error palette."); + assert.match(css, + /\[role="alert"\]\.alert-root\[class~="bg-token-dropdown-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-info-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-info-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-success-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-warning-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-warning-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-error-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-danger-surface\)/, + "Every native toast level must resolve to its own semantic alert palette."); + assert.match(css, + /\[role="alert"\]\.alert-root\s*\{[\s\S]*?background-color:\s*var\(--ds-alert-surface,\s*var\(--ds-popover\)\)\s*!important[\s\S]*?border-color:\s*var\(--ds-alert-border,\s*var\(--ds-border\)\)\s*!important[\s\S]*?color:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)\s*!important[\s\S]*?--color-token-text-secondary:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)/, + "Native alerts must carry readable semantic surface, border and inherited foreground roles."); + assert.match(css, + /\[role="alert"\]\.alert-root > button\[aria-label\]\s*\{[\s\S]*?color:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)\s*!important[\s\S]*?opacity:\s*\.76\s*!important/, + "Alert close controls must remain visibly distinct without changing their native hit target."); + assert.doesNotMatch(css, + /--ds-art-clear-rail|padding-inline-(?:start|end):\s*var\(--ds-art-clear-rail\)/, + "Artwork must adapt to the application without reserving or shrinking native content geometry."); + assert.doesNotMatch(css, + /data-dream-art-safe(?:-area)?="(?:left|right)"[^{]*\{[^}]*--ds-art-position:/, + "Safe-side atmosphere metadata must not override the theme's explicit artwork focus point."); + assert.match(css, + /@media \(max-width:\s*1120px\)[\s\S]*?--ambient-start:\s*52%/, + "Narrow windows must preserve the same comfortable full-width reading transition."); + assert.match(css, + /main\.main-surface:not\(:has\(\[role="main"\]\)\)\s+article\s*\{[\s\S]*?background:\s*var\(--message-surface\)[\s\S]*?backdrop-filter:\s*blur\(14px\)\s+saturate\(108%\)/, + "Task messages must remain legible glass surfaces instead of hiding the artwork."); + assert.match(css, + /body\s*\{[\s\S]*?background-image:\s*var\(--dream-skin-art\)\s*!important[\s\S]*?main\.main-surface:not\(:has\(\[role="main"\]\)\)\s*\{[\s\S]*?overflow:\s*visible\s*!important[\s\S]*?background:\s*var\(--reading-veil\)\s*!important[\s\S]*?\.thread-scroll-container\s*\{[\s\S]*?background:\s*transparent\s*!important/, + "Wide ambient tasks must use one body artwork layer plus one main-canvas reading veil."); + const threadSurfaceBlocks = [...css.matchAll( + /([^{}]*\.thread-scroll-container[^{}]*)\{([^{}]*)\}/g, + )]; + assert.ok(threadSurfaceBlocks.length > 0, + "The renderer CSS fixture must include the native thread surface."); + for (const [, selector, declarations] of threadSurfaceBlocks) { + assert.doesNotMatch( + declarations, + /(?:^|\n)\s*(?:overflow(?:-[xy])?|clip-path|(?:-webkit-)?mask(?:-image)?)\s*:/, + `The skin must not override native scrolling or clipping on ${selector.trim()}.`, + ); + } + assert.doesNotMatch(css, /calc\(100%\s*-\s*(?:420|240)px\)/, + "Single-canvas atmosphere must not use viewport-specific right-column breakpoints."); + assert.match(css, + /main\.main-surface:not\(:has\(\[role="main"\]\)\)::after\s*\{[\s\S]*?content:\s*none\s*!important[\s\S]*?display:\s*none\s*!important/, + "Wide ambient task routes must not create an independently bounded artwork window."); + assert.doesNotMatch(css, /--ds-task-art-window|mask-image:(?!\s*none)/, + "The task atmosphere must not rely on a second mask with its own edge."); + assert.match(css, + /header\.app-header-tint\s*\{[\s\S]*?background:\s*var\(--composer-surface\)\s*!important[\s\S]*?border:\s*0\s*!important[\s\S]*?box-shadow:\s*none\s*!important/, + "Theme chrome must not draw a full-width divider below the native toolbar."); + assert.match(css, + /composer-surface-chrome::before\s*\{[\s\S]*?content:\s*""\s*!important[\s\S]*?border:\s*1px solid var\(--ds-border\)[\s\S]*?pointer-events:\s*none\s*!important[\s\S]*?composer-surface-chrome:focus-within\s*\{[\s\S]*?outline:\s*2px solid var\(--ds-focus\)[\s\S]*?outline-offset:\s*2px/, + "The composer must expose a stable one-pixel boundary layer and a two-pixel focus ring."); + assert.match(css, + /composer-surface-chrome\s*\{[\s\S]{0,260}?isolation:\s*isolate\s*!important[\s\S]{0,120}?z-index:\s*4\s*!important[\s\S]{0,260}?overflow:\s*visible\s*!important[\s\S]{0,260}?clip-path:\s*none\s*!important[\s\S]{0,260}?mask-image:\s*none\s*!important[\s\S]{0,260}?background-clip:\s*padding-box\s*!important[\s\S]{0,260}?background:\s*var\(--composer-surface\)\s*!important[\s\S]{0,180}?backdrop-filter:\s*blur\(20px\)\s+saturate\(108%\)\s*!important/, + "The composer must remain an independent, complete surface above atmospheric layers."); + assert.doesNotMatch(css, + /composer-surface-chrome\s*\{[\s\S]{0,240}?border:\s*0\s*!important/, + "No composer variant may remove the input boundary."); + assert.match(css, + /data-dream-shell="light"\][\s\S]*?composer-surface-chrome p\.placeholder::after\s*\{[\s\S]*?color:\s*var\(--ds-text-secondary\)\s*!important[\s\S]*?opacity:\s*1\s*!important/, + "Light-shell composer placeholders must keep opaque secondary-text contrast."); + assert.match(css, + /:is\(\[role="menu"\],\s*\[role="dialog"\]\)[\s\S]*?background:\s*var\(--ds-popover\)[\s\S]*?border:\s*1px solid var\(--ds-border\)/, + "Menus and dialogs must use an independent elevated popover surface."); // Every home/project selector must stay behind the root skin gate. A // marker-class-to-:has() conversion must never leave native layout rules // active after pause/restore. @@ -281,8 +416,9 @@ export async function runRendererRuntimeTest(assetRoot) { assert.equal(home.document.adoptedStyleSheets.length, 1); assert.equal(state.scope.baseState, "home"); assert.equal(state.scope.level, "L1"); - assert.equal(home.rootStyle.values.get("--dream-skin-brand-subtitle"), '"CODEX DREAM SKIN"'); - assert.equal(home.rootStyle.values.get("--dream-skin-status"), '"DREAM SKIN ONLINE"'); + assert.equal(home.rootStyle.values.get("--dream-skin-brand-subtitle"), undefined); + assert.equal(home.rootStyle.values.get("--dream-skin-status"), undefined); + assert.equal(home.rootStyle.values.get("--dream-skin-quote"), undefined); assert.equal(home.rootStyle.values.get("--ds-theme-surface-radius"), "12px"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-opacity"), "1"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-blur"), "0px"); diff --git a/tools/selectors.json b/tools/selectors.json index 06e22231..ea0f0994 100644 --- a/tools/selectors.json +++ b/tools/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/windows/VERSION b/windows/VERSION index 9075be49..eac1e0ad 100644 --- a/windows/VERSION +++ b/windows/VERSION @@ -1 +1 @@ -1.5.5 +1.5.6 diff --git a/windows/assets/dream-skin.css b/windows/assets/dream-skin.css index bd112ec0..a3a580ed 100644 --- a/windows/assets/dream-skin.css +++ b/windows/assets/dream-skin.css @@ -59,6 +59,70 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* One-canvas layout contract. Native window chrome and content geometry + remain authoritative; artwork must adapt to the product, never reserve + layout space at the expense of typing or reading comfort. */ + --titlebar-safe-left: max(env(safe-area-inset-left), 0px); + --titlebar-safe-right: max(env(safe-area-inset-right), 0px); + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + /* Semantic design tokens. Theme packages provide the primitive palette; + native components consume these roles so material, state and accessibility + decisions stay consistent instead of scattering alpha values. */ + --ds-canvas: var(--ds-bg); + --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); + --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); + --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); + --reading-veil: linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); + --ds-surface: var(--ds-panel); + --message-surface: rgb(var(--ds-panel-rgb) / .94); + --ds-elevated-surface: var(--ds-panel); + --ds-popover: rgb(var(--ds-panel-rgb) / .988); + --ds-input: rgb(var(--ds-panel-rgb) / .985); + --composer-surface: rgb(var(--ds-panel-rgb) / .92); + --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); + --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); + --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); + --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); + --ds-focus: var(--ds-accent); + --ds-border: var(--ds-line); + --ds-text-primary: var(--ds-text); + --ds-text-secondary: var(--ds-muted); + --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); + --ds-danger: #ff9a8f; + --ds-danger-surface: #4b1f22; + --ds-danger-foreground: #fff0ed; + --ds-danger-border: #d87970; + --ds-success: #80c79b; + --ds-success-surface: #203d2d; + --ds-success-foreground: #e6f4e9; + --ds-success-border: #72a782; + --ds-warning-surface: #4c351c; + --ds-warning-foreground: #fff0cf; + --ds-warning-border: #cf984b; + --ds-info-surface: #223842; + --ds-info-foreground: #edf7fa; + --ds-info-border: #7899a6; + --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); + --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); + --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); + --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); + --ds-zone-shadow: + 0 7px 18px rgb(var(--ds-bg-rgb) / .09), + inset 0 1px rgb(var(--ds-panel-rgb) / .82); + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); + --ds-shadow-elevated: + 0 16px 38px rgb(var(--ds-bg-rgb) / .20), + 0 2px 8px rgb(var(--ds-bg-rgb) / .12); + --ds-motion-fast: 120ms; + --ds-motion-standard: 160ms; + --ds-motion-reveal: 210ms; + --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -107,6 +171,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); + --ds-danger: #9c3d3a; + --ds-danger-surface: #f8e6e3; + --ds-danger-foreground: #6f2523; + --ds-danger-border: #b85650; + --ds-success: #45624d; + --ds-success-surface: #e4efe5; + --ds-success-foreground: #28543a; + --ds-success-border: #5f856b; + --ds-warning-surface: #f8edd7; + --ds-warning-foreground: #674515; + --ds-warning-border: #aa7a32; + --ds-info-surface: #e8eef1; + --ds-info-foreground: #284754; + --ds-info-border: #6f8b97; + --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); + --ds-shadow-elevated: + 0 18px 42px rgb(var(--ds-bg-rgb) / .16), + 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -115,6 +197,131 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); + --vscode-inputValidation-errorBackground: var(--ds-danger-surface); + --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); + --vscode-inputValidation-errorBorder: var(--ds-danger-border); + --color-token-input-validation-error-background: var(--ds-danger-surface); + --color-token-input-validation-error-foreground: var(--ds-danger-foreground); + --color-token-input-validation-error-border: var(--ds-danger-border); + --color-token-error-foreground: var(--ds-danger-foreground); + --color-token-editor-error-foreground: var(--ds-danger-foreground); +} + +/* Codex can remain electron-dark while a theme explicitly requests a light + shell. Bridge the native token family to the active skin so task cards, + inputs, secondary copy and icons do not keep dark-mode values on a light + wallpaper. This is component-scoped token remapping, never whole-page + opacity. */ +html[data-dream-skin="active"][data-dream-shell="light"] { + --vscode-foreground: var(--ds-text); + --vscode-descriptionForeground: var(--ds-muted); + --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); + --color-background-panel: var(--ds-panel); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); + --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); + --color-token-conversation-body: var(--ds-muted); + --color-token-conversation-summary-leading: var(--ds-muted); + --color-token-conversation-summary-trailing: var(--ds-muted); + --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); + --color-token-description-foreground: var(--ds-muted); + --color-token-list-hover-background: var(--ds-hover); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* The scheduled-task detail side panel uses a separate inline panel surface + variable and input token family. Reinforce the light-shell bridge at its + native form boundary so the prompt, detail and schedule cards cannot retain + electron-dark surfaces or pale input copy. */ +html[data-dream-skin="active"][data-dream-shell="light"] + [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { + color: var(--ds-text); + --color-background-panel: var(--ds-input); + --color-token-bg-fog: var(--ds-panel-2); + --color-token-bg-secondary: var(--ds-panel-2); + --color-token-main-surface-primary: var(--ds-panel); + --color-token-main-surface-secondary: var(--ds-panel-2); + --color-token-foreground: var(--ds-text); + --color-token-text-primary: var(--ds-text); + --color-token-text-secondary: var(--ds-muted); + --color-token-description-foreground: var(--ds-muted); + --color-token-input-background: var(--ds-input); + --color-token-input-foreground: var(--ds-text); + --color-token-input-placeholder-foreground: var(--ds-muted); + --color-token-border: var(--ds-border); + --color-token-border-default: var(--ds-border); +} + +/* Native toasts use one generic foreground token across four different + surfaces. Resolve the actual host state classes into independent semantic + palettes before styling the shared alert component. */ +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-dropdown-background"] { + --ds-alert-surface: var(--ds-info-surface); + --ds-alert-foreground: var(--ds-info-foreground); + --ds-alert-border: var(--ds-info-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { + --ds-alert-surface: var(--ds-success-surface); + --ds-alert-foreground: var(--ds-success-foreground); + --ds-alert-border: var(--ds-success-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { + --ds-alert-surface: var(--ds-warning-surface); + --ds-alert-foreground: var(--ds-warning-foreground); + --ds-alert-border: var(--ds-warning-border); +} + +html[data-dream-skin="active"] + [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { + --ds-alert-surface: var(--ds-danger-surface); + --ds-alert-foreground: var(--ds-danger-foreground); + --ds-alert-border: var(--ds-danger-border); +} + +html[data-dream-skin="active"] [role="alert"].alert-root { + background-color: var(--ds-alert-surface, var(--ds-popover)) !important; + border-color: var(--ds-alert-border, var(--ds-border)) !important; + color: var(--ds-alert-foreground, var(--ds-text)) !important; + --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); + --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { + color: var(--ds-alert-foreground, var(--ds-text)) !important; + opacity: .76 !important; +} + +html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { + opacity: 1 !important; +} + +/* Active tool/agent summaries render duplicated shimmer text with hard-coded + alpha and a white sweep. Keep the base label readable and tint only its + moving overlay with the skin accent; no ancestor opacity is changed. */ +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { + color: var(--ds-muted) !important; + -webkit-text-fill-color: var(--ds-muted) !important; +} + +html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { + color: var(--ds-accent) !important; + -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -130,12 +337,10 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; - --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; - --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -218,9 +423,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] aside.app-shell-left-panel { background: linear-gradient(180deg, - rgb(var(--ds-panel-rgb) / .98), - rgb(var(--ds-bg-rgb) / .96)) !important; - border: 1px solid var(--ds-line) !important; + var(--ds-sidebar), + rgb(var(--ds-bg-rgb) / .975)) !important; + border: 1px solid var(--ds-border) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -230,12 +435,53 @@ html[data-dream-skin="active"] aside.app-shell-left-panel { html[data-dream-skin="active"] aside.app-shell-left-panel nav { background: transparent !important; } +/* Recent and other semantic lists may read as quiet raised sections. The + primary navigation stays on the native flat canvas: wrapping its split + fixed/scrolling DOM in a faux card creates the broken right edge called out + in the screenshot and relies on brittle nth-child geometry. */ +html[data-dream-skin="active"] aside.app-shell-left-panel + nav section:has([role="list"]) { + box-sizing: border-box !important; + margin-inline: 8px !important; + padding: 7px 6px 8px !important; + border: 1px solid var(--ds-zone-border) !important; + border-radius: 14px !important; + background: var(--ds-zone-surface-raised) !important; + box-shadow: var(--ds-zone-shadow) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel + nav section:has([role="list"]) > div > div:first-child { + margin-bottom: 3px !important; + padding-bottom: 5px !important; + border-bottom: 1px solid var(--ds-zone-divider) !important; +} + +/* Use the existing 16px native resize target as the visual boundary. The line + is non-interactive and never narrows or covers the resizer hit area. */ +html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]::before { + content: "" !important; + position: absolute !important; + top: 0 !important; + bottom: 0 !important; + left: 8px !important; + width: 1px !important; + background: var(--ds-zone-border) !important; + box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]:hover::before { + background: rgb(var(--ds-accent-rgb) / .52) !important; +} + html[data-dream-skin="active"] aside.app-shell-left-panel button, html[data-dream-skin="active"] aside.app-shell-left-panel a { color: var(--ds-text) !important; - transition: background-color .18s cubic-bezier(.22, 1, .36, 1), - border-color .18s cubic-bezier(.22, 1, .36, 1), - color .18s cubic-bezier(.22, 1, .36, 1) !important; + transition: background-color var(--ds-motion-standard) var(--ds-ease-out), + border-color var(--ds-motion-standard) var(--ds-ease-out), + color var(--ds-motion-fast) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; } html[data-dream-skin="active"] aside.app-shell-left-panel [class*="text-token-foreground"] { @@ -251,8 +497,9 @@ html[data-dream-skin="active"] aside.app-shell-left-panel svg { } html[data-dream-skin="active"] aside.app-shell-left-panel button:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel a:hover { - background: rgb(var(--ds-accent-rgb) / .09) !important; +html[data-dream-skin="active"] aside.app-shell-left-panel a:hover, +html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:hover { + background: var(--ds-hover) !important; color: var(--ds-text) !important; transform: none !important; } @@ -279,9 +526,23 @@ html[data-dream-skin="active"] aside.app-shell-left-panel button[aria-label^=" html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] { - background: rgb(var(--ds-accent-rgb) / .12) !important; - border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; - box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; + background: var(--ds-selected) !important; + border: 1px solid var(--ds-accent) !important; + border-radius: 9px 9px 10px 10px !important; + box-shadow: + 0 4px 14px rgb(var(--ds-bg-rgb) / .14), + inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; + color: var(--ds-text) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:active, +html[data-dream-skin="active"] aside.app-shell-left-panel button:active, +html[data-dream-skin="active"] aside.app-shell-left-panel a:active { + background: var(--ds-pressed) !important; +} + +html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] [class*="text-token"], +html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"] [class*="text-token"] { color: var(--ds-text) !important; } @@ -289,6 +550,18 @@ html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] color: var(--ds-accent) !important; } +/* Onboarding and task suggestion cards are native sidebar surfaces, not + dialogs. When Codex itself is dark they otherwise remain charcoal beneath a + light skin. Keep them opaque enough for copy while preserving a soft lift. */ +html[data-dream-skin="active"][data-dream-shell="light"] aside.app-shell-left-panel + [class~="bg-token-main-surface-primary/70"] { + background: var(--ds-selected) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-surface) !important; + color: var(--ds-text) !important; + backdrop-filter: blur(14px) saturate(104%) !important; +} + html[data-dream-skin="active"] main.main-surface { position: relative !important; isolation: isolate; @@ -411,10 +684,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -425,21 +699,29 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } +/* The artwork is painted once on the fixed body layer. Do not create a second + right-side image window: its own width, mask and vertical bounds form a + visible page seam and can bisect the sticky composer region. */ +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"]))::after { + content: none !important; + display: none !important; +} + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(14px) saturate(102%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface > header.app-header-tint { - background: transparent !important; - border-bottom: 0 !important; + background: var(--composer-surface) !important; + border: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -454,9 +736,7 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] main.main-surface:not(:has([role="main"])) [class*="_markdown"] { - text-shadow: - 0 1px 2px rgb(var(--ds-panel-rgb) / .92), - 0 0 10px rgb(var(--ds-panel-rgb) / .72); + text-shadow: none; } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -493,6 +773,26 @@ html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) [role= 0 0 10px rgb(var(--ds-bg-rgb) / .46); } +/* Non-immersive task routes keep a conservative reading surface. Wide ambient + routes clear this local layer below because their veil already lives on the + complete main canvas, including the composer zone. */ +html[data-dream-skin="active"] + main.main-surface:not(:has([role="main"])) + .thread-scroll-container { + background: + linear-gradient(90deg, + var(--ds-content-reading-plane) 0%, + var(--ds-content-reading-plane) var(--ambient-start), + var(--ds-content-reading-plane-far) 100%) !important; + backdrop-filter: none !important; +} + +html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"])) + .thread-scroll-container { + background: transparent !important; +} + html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) [role="main"] { text-shadow: none; } @@ -510,19 +810,19 @@ html[data-dream-skin="active"] main.main-surface html[data-dream-skin="active"] main.main-surface div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: rgb(var(--ds-panel-rgb) / .62) !important; + background: var(--ds-content-reading-plane) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: rgb(var(--ds-panel-rgb) / .44); + background: var(--message-surface); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(7px) saturate(105%); + backdrop-filter: blur(14px) saturate(108%); } html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) article { - background: rgb(var(--ds-panel-rgb) / .72); + background: var(--message-surface); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -532,70 +832,25 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets. A solid surface + is enough; a theme border/shadow becomes a full-width false divider. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity comes from the artwork and palette, so task chrome carries no + absolute-positioned labels and the home composer has no floating quote. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +1144,81 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-border) !important; + border-width: 1px !important; + border-style: solid !important; + border-color: var(--ds-border) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; - box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + background: var(--composer-surface) !important; + box-shadow: var(--ds-shadow-surface) !important; + backdrop-filter: blur(20px) saturate(108%) !important; + transition: + border-color var(--ds-motion-standard) var(--ds-ease-out), + box-shadow var(--ds-motion-standard) var(--ds-ease-out), + background-color var(--ds-motion-standard) var(--ds-ease-out) !important; +} + +/* Current Codex builds force the native composer border width to zero from a + higher-priority cascade layer. Draw the semantic 1px boundary separately so + it remains stable without changing content opacity or input geometry. */ +html[data-dream-skin="active"] .composer-surface-chrome::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-border) !important; + border-radius: inherit !important; + pointer-events: none !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:hover { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:hover::before { + border-color: var(--ds-accent) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:focus-within { + border-color: var(--ds-focus) !important; + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; + box-shadow: var(--ds-shadow-elevated) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:focus-within::before { + border-color: var(--ds-focus) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled), +html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"] { + color: var(--ds-text-muted) !important; + background: var(--ds-input-disabled) !important; + border-color: var(--ds-border) !important; + box-shadow: none !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled)::before, +html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"]::before { + border-color: var(--ds-border) !important; +} + +html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"]) { + border-color: var(--ds-danger) !important; + outline-color: var(--ds-danger) !important; } -html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } +html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"])::before { + border-color: var(--ds-danger) !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +1301,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-border) !important; box-shadow: - 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), + var(--ds-shadow-surface), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -995,12 +1315,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] .composer-surface-chrome p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .78) !important; + color: var(--ds-text-secondary) !important; opacity: 1 !important; } @@ -1013,7 +1333,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1023,7 +1343,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"]) .composer-surface-chrome { - border: 0 !important; + border: 1px solid var(--ds-border) !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1033,12 +1353,12 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has( } html[data-dream-skin="active"][data-dream-art-wide="true"] [class*="_homeUtilityBar_"] { - background: var(--ds-immersive-composer-solid) !important; + background: var(--composer-surface) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(20px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1056,7 +1376,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(8px) saturate(102%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"] [class*="_homeUtilityBar_"] button, @@ -1081,7 +1401,55 @@ html[data-dream-skin="active"] .composer-surface-chrome button:not([class~="bg-t } html[data-dream-skin="active"] .composer-surface-chrome p.placeholder::after { - color: rgb(var(--ds-muted-rgb) / .82) !important; + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +html[data-dream-skin="active"] :is(input, textarea)::placeholder { + color: var(--ds-text-secondary) !important; + opacity: 1 !important; +} + +/* Menus, popovers and dialogs are independent elevated surfaces. Background + content may inform their material tint but must never remain legible through + them. */ +html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), +html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { + color: var(--ds-text-primary) !important; + background: var(--ds-popover) !important; + border: 1px solid var(--ds-border) !important; + box-shadow: var(--ds-shadow-elevated) !important; + backdrop-filter: blur(18px) saturate(102%) !important; +} + +html[data-dream-skin="active"] :is( + button, + a, + input, + textarea, + [role="button"], + [role="menuitem"], + [tabindex] +):focus-visible { + outline: 2px solid var(--ds-focus) !important; + outline-offset: 2px !important; +} + +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +):disabled, +html[data-dream-skin="active"] :is( + button, + input, + textarea, + [role="button"], + [role="menuitem"] +)[aria-disabled="true"] { + color: var(--ds-text-muted) !important; opacity: 1 !important; } @@ -1173,12 +1541,26 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { + background: var(--ds-highlight) !important; +} + +html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { + transform: translateY(1px); + box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; +} + html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { + html[data-dream-skin="active"] { + --ambient-start: 52%; + } html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { content: none; } + html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] + main.main-surface:not(:has([role="main"]))::after { content: none; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/windows/assets/renderer-inject.js b/windows/assets/renderer-inject.js index be799888..9ef10592 100644 --- a/windows/assets/renderer-inject.js +++ b/windows/assets/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Keep the old property names in + // THEME_VARIABLES so reinjection removes values left by earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/windows/assets/selectors.json b/windows/assets/selectors.json index 06e22231..ea0f0994 100644 --- a/windows/assets/selectors.json +++ b/windows/assets/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/windows/scripts/injector.mjs b/windows/scripts/injector.mjs index ebedabf7..e55f936c 100644 --- a/windows/scripts/injector.mjs +++ b/windows/scripts/injector.mjs @@ -39,7 +39,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.5"; +const SKIN_VERSION = "1.5.6"; const MAX_ART_BYTES = 10 * 1024 * 1024; const MAX_SAFE_CSS_BYTES = 256 * 1024; const STRONG_THEME_AUDIT_MS = 30000; @@ -1129,6 +1129,14 @@ export async function verifySession( ?? chainCandidates.findLast((item) => item?.visible) ?? siblingCandidates.find((item) => item?.visible) ?? box(boxableChain[boxableChain.length - 1]); + const threadSurfaceNode = document.querySelector(${selectorLiteral("thread-surface")}); + const threadSurfaceStyle = threadSurfaceNode ? getComputedStyle(threadSurfaceNode) : null; + const threadSurface = threadSurfaceNode ? { + ...box(threadSurfaceNode), + overflowX: threadSurfaceStyle.overflowX, + overflowY: threadSurfaceStyle.overflowY, + pointerEvents: threadSurfaceStyle.pointerEvents, + } : null; const result = { installed: document.documentElement.getAttribute('data-dream-skin') === 'active', version: runtime?.version ?? null, @@ -1150,6 +1158,7 @@ export async function verifySession( composer: box(document.querySelector(${selectorLiteral("composer-chrome")})), shell: box(document.querySelector(${selectorLiteral("shell-main")})), sidebar: box(document.querySelector(${selectorLiteral("left-panel")})), + threadSurface, nativeWindow: ${JSON.stringify(nativeWindow)}, documentVisibility: document.visibilityState ?? null, documentHidden: document.hidden === true, @@ -1166,6 +1175,11 @@ export async function verifySession( const documentPass = result.documentVisibility === 'visible' && !result.documentHidden; const viewportPass = result.viewport.width >= ${MIN_RENDERER_VIEWPORT_WIDTH} && result.viewport.height >= ${MIN_RENDERER_VIEWPORT_HEIGHT}; + const threadScrollPass = !result.threadSurface || ( + result.threadSurface.visible && + ['auto', 'scroll', 'overlay'].includes(result.threadSurface.overflowY) && + result.threadSurface.pointerEvents !== 'none' + ); const windowPass = result.nativeWindow?.pass === true; const expectedThemeId = ${JSON.stringify(expectedThemeId)}; const expectedRevision = ${JSON.stringify(expectedRevision)}; @@ -1173,10 +1187,16 @@ export async function verifySession( (!expectedRevision || result.revision === expectedRevision); result.expectedThemeId = expectedThemeId; result.expectedRevision = expectedRevision; - result.readiness = { windowPass, documentPass, viewportPass, structurePass }; + result.readiness = { + windowPass, + documentPass, + viewportPass, + structurePass, + threadScrollPass, + }; result.pass = result.installed && result.version === result.expectedVersion && result.stylePresent && result.businessClassPollution === 0 && windowPass && - documentPass && viewportPass && structurePass && + documentPass && viewportPass && structurePass && threadScrollPass && payloadPass && (!result.homePresent || (Boolean(result.homeSurface?.visible && result.hero?.visible) && (!result.suggestionsPresent || (result.cards.length >= 2 && result.cards.length <= 4)))); diff --git a/windows/tests/injector-window-readiness.test.mjs b/windows/tests/injector-window-readiness.test.mjs index 4a691b11..ba5e4074 100644 --- a/windows/tests/injector-window-readiness.test.mjs +++ b/windows/tests/injector-window-readiness.test.mjs @@ -13,6 +13,7 @@ const selectors = { shell: "main.main-surface", sidebar: "aside.app-shell-left-panel", composer: ".composer-surface-chrome", + threadSurface: ".thread-scroll-container", home: '[role="main"]:has([data-testid="home-icon"])', settings: 'input[name="appearance-theme"]', themePreview: '[data-testid="theme-preview"]', @@ -50,6 +51,9 @@ function makeDomFixture({ shell = makeElement(), sidebar = makeElement(), composer = makeElement(), + threadSurface = makeElement({ + style: { overflowX: "hidden", overflowY: "auto", pointerEvents: "auto" }, + }), home = null, settings = null, visibilityState = "visible", @@ -74,6 +78,7 @@ function makeDomFixture({ if (selector === selectors.shell) return shell; if (selector === selectors.sidebar) return sidebar; if (selector === selectors.composer) return composer; + if (selector === selectors.threadSurface) return threadSurface; if (selector === selectors.home) return home; if (selector === selectors.settings || selector === selectors.themePreview) return settings; return null; @@ -83,7 +88,7 @@ function makeDomFixture({ }; const window = { __CODEX_DREAM_SKIN_STATE__: { - version: "1.5.5", + version: "1.5.6", themeId: "fixture-theme", revision: "fixture-revision", styleMode: "style", @@ -148,6 +153,7 @@ test("normal L1 renderer requires and records the exact target window binding", documentPass: true, viewportPass: true, structurePass: true, + threadScrollPass: true, }); assert.deepEqual(session.calls, [ { method: "Browser.getWindowForTarget", params: { targetId: "page-main" } }, @@ -155,6 +161,18 @@ test("normal L1 renderer requires and records the exact target window binding", ]); }); +test("task verification fails when the skin disables the native thread scroller", async () => { + const broken = await verify({ + dom: makeDomFixture({ + threadSurface: makeElement({ + style: { overflowX: "visible", overflowY: "visible", pointerEvents: "auto" }, + }), + }), + }); + assert.equal(broken.result.pass, false); + assert.equal(broken.result.readiness.threadScrollPass, false); +}); + test("visible settings and home anchors are the only L0 structure exceptions", async () => { const settings = await verify({ dom: makeDomFixture({ From d8cad6321f4a1ff508c972cd043861605f4f3428 Mon Sep 17 00:00:00 2001 From: StephenHsu Date: Sun, 26 Jul 2026 06:59:17 -0400 Subject: [PATCH 2/3] Revert "Fix themed layout seams and native scrolling" This reverts commit 4fbab37bbc792e7802467e212f6d9f1984ddeddc. --- macos/CHANGELOG.md | 9 - macos/VERSION | 2 +- macos/assets/dream-skin.css | 596 ++++-------------- macos/assets/renderer-inject.js | 8 +- macos/assets/selectors.json | 2 +- macos/package.json | 2 +- macos/scripts/common-macos.sh | 2 +- macos/scripts/injector.mjs | 19 +- macos/tests/run-tests.sh | 4 +- macos/tests/window-readiness.test.mjs | 22 - runtime/dream-skin.css | 596 ++++-------------- runtime/renderer-inject.js | 8 +- tools/renderer-runtime.test.mjs | 146 +---- tools/selectors.json | 2 +- windows/VERSION | 2 +- windows/assets/dream-skin.css | 596 ++++-------------- windows/assets/renderer-inject.js | 8 +- windows/assets/selectors.json | 2 +- windows/scripts/injector.mjs | 26 +- .../tests/injector-window-readiness.test.mjs | 20 +- 20 files changed, 356 insertions(+), 1716 deletions(-) diff --git a/macos/CHANGELOG.md b/macos/CHANGELOG.md index 8ac945a2..349b5101 100644 --- a/macos/CHANGELOG.md +++ b/macos/CHANGELOG.md @@ -1,14 +1,5 @@ # Changelog -## 1.5.6 — 2026-07-26 - -### 修复 - -- 移除会覆盖原生标题栏的主题标语与定位规则,不再调整返回、前进、侧栏和窗口控件的原生布局,恢复左右工具栏的安全间距、点击区域与可读性。 -- 将宽幅任务背景重构为单一画布:画作只存在于最底层,连续阅读遮罩在同一主平面过渡,不再创建独立的右侧图片窗口;输入框同时改为完整、独立的高层 surface,避免背景、遮罩、裁切或层级造成右端断裂。 -- 补齐浅色主题对原生面板、会话摘要、输入、弹窗、提示状态和选中态的语义色映射,避免深色宿主 token 穿透后产生低对比度文字或表面。 -- 注入验证现在会检查原生会话滚动容器仍可滚动;主题若覆盖其 `overflow`、裁切或交互能力,将安全失败而不是提交一个无法使用的活动状态。 - ## 1.5.5 — 2026-07-25 ### 修复 diff --git a/macos/VERSION b/macos/VERSION index eac1e0ad..9075be49 100644 --- a/macos/VERSION +++ b/macos/VERSION @@ -1 +1 @@ -1.5.6 +1.5.5 diff --git a/macos/assets/dream-skin.css b/macos/assets/dream-skin.css index a3a580ed..bd112ec0 100644 --- a/macos/assets/dream-skin.css +++ b/macos/assets/dream-skin.css @@ -59,70 +59,6 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); - /* One-canvas layout contract. Native window chrome and content geometry - remain authoritative; artwork must adapt to the product, never reserve - layout space at the expense of typing or reading comfort. */ - --titlebar-safe-left: max(env(safe-area-inset-left), 0px); - --titlebar-safe-right: max(env(safe-area-inset-right), 0px); - --ambient-start: 52%; - --ambient-feather: clamp(240px, 24vw, 320px); - /* Semantic design tokens. Theme packages provide the primitive palette; - native components consume these roles so material, state and accessibility - decisions stay consistent instead of scattering alpha values. */ - --ds-canvas: var(--ds-bg); - --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); - --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); - --reading-veil: linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), - rgb(var(--ds-panel-rgb) / .06) 100%); - --ds-surface: var(--ds-panel); - --message-surface: rgb(var(--ds-panel-rgb) / .94); - --ds-elevated-surface: var(--ds-panel); - --ds-popover: rgb(var(--ds-panel-rgb) / .988); - --ds-input: rgb(var(--ds-panel-rgb) / .985); - --composer-surface: rgb(var(--ds-panel-rgb) / .92); - --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); - --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); - --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); - --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); - --ds-focus: var(--ds-accent); - --ds-border: var(--ds-line); - --ds-text-primary: var(--ds-text); - --ds-text-secondary: var(--ds-muted); - --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); - --ds-danger: #ff9a8f; - --ds-danger-surface: #4b1f22; - --ds-danger-foreground: #fff0ed; - --ds-danger-border: #d87970; - --ds-success: #80c79b; - --ds-success-surface: #203d2d; - --ds-success-foreground: #e6f4e9; - --ds-success-border: #72a782; - --ds-warning-surface: #4c351c; - --ds-warning-foreground: #fff0cf; - --ds-warning-border: #cf984b; - --ds-info-surface: #223842; - --ds-info-foreground: #edf7fa; - --ds-info-border: #7899a6; - --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); - --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); - --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); - --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); - --ds-zone-shadow: - 0 7px 18px rgb(var(--ds-bg-rgb) / .09), - inset 0 1px rgb(var(--ds-panel-rgb) / .82); - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); - --ds-shadow-elevated: - 0 16px 38px rgb(var(--ds-bg-rgb) / .20), - 0 2px 8px rgb(var(--ds-bg-rgb) / .12); - --ds-motion-fast: 120ms; - --ds-motion-standard: 160ms; - --ds-motion-reveal: 210ms; - --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -171,24 +107,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); - --ds-danger: #9c3d3a; - --ds-danger-surface: #f8e6e3; - --ds-danger-foreground: #6f2523; - --ds-danger-border: #b85650; - --ds-success: #45624d; - --ds-success-surface: #e4efe5; - --ds-success-foreground: #28543a; - --ds-success-border: #5f856b; - --ds-warning-surface: #f8edd7; - --ds-warning-foreground: #674515; - --ds-warning-border: #aa7a32; - --ds-info-surface: #e8eef1; - --ds-info-foreground: #284754; - --ds-info-border: #6f8b97; - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); - --ds-shadow-elevated: - 0 18px 42px rgb(var(--ds-bg-rgb) / .16), - 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -197,131 +115,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); - --vscode-inputValidation-errorBackground: var(--ds-danger-surface); - --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); - --vscode-inputValidation-errorBorder: var(--ds-danger-border); - --color-token-input-validation-error-background: var(--ds-danger-surface); - --color-token-input-validation-error-foreground: var(--ds-danger-foreground); - --color-token-input-validation-error-border: var(--ds-danger-border); - --color-token-error-foreground: var(--ds-danger-foreground); - --color-token-editor-error-foreground: var(--ds-danger-foreground); -} - -/* Codex can remain electron-dark while a theme explicitly requests a light - shell. Bridge the native token family to the active skin so task cards, - inputs, secondary copy and icons do not keep dark-mode values on a light - wallpaper. This is component-scoped token remapping, never whole-page - opacity. */ -html[data-dream-skin="active"][data-dream-shell="light"] { - --vscode-foreground: var(--ds-text); - --vscode-descriptionForeground: var(--ds-muted); - --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); - --color-background-panel: var(--ds-panel); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); - --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); - --color-token-conversation-body: var(--ds-muted); - --color-token-conversation-summary-leading: var(--ds-muted); - --color-token-conversation-summary-trailing: var(--ds-muted); - --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); - --color-token-description-foreground: var(--ds-muted); - --color-token-list-hover-background: var(--ds-hover); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* The scheduled-task detail side panel uses a separate inline panel surface - variable and input token family. Reinforce the light-shell bridge at its - native form boundary so the prompt, detail and schedule cards cannot retain - electron-dark surfaces or pale input copy. */ -html[data-dream-skin="active"][data-dream-shell="light"] - [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { - color: var(--ds-text); - --color-background-panel: var(--ds-input); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: var(--ds-muted); - --color-token-description-foreground: var(--ds-muted); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* Native toasts use one generic foreground token across four different - surfaces. Resolve the actual host state classes into independent semantic - palettes before styling the shared alert component. */ -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-dropdown-background"] { - --ds-alert-surface: var(--ds-info-surface); - --ds-alert-foreground: var(--ds-info-foreground); - --ds-alert-border: var(--ds-info-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { - --ds-alert-surface: var(--ds-success-surface); - --ds-alert-foreground: var(--ds-success-foreground); - --ds-alert-border: var(--ds-success-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { - --ds-alert-surface: var(--ds-warning-surface); - --ds-alert-foreground: var(--ds-warning-foreground); - --ds-alert-border: var(--ds-warning-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { - --ds-alert-surface: var(--ds-danger-surface); - --ds-alert-foreground: var(--ds-danger-foreground); - --ds-alert-border: var(--ds-danger-border); -} - -html[data-dream-skin="active"] [role="alert"].alert-root { - background-color: var(--ds-alert-surface, var(--ds-popover)) !important; - border-color: var(--ds-alert-border, var(--ds-border)) !important; - color: var(--ds-alert-foreground, var(--ds-text)) !important; - --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { - color: var(--ds-alert-foreground, var(--ds-text)) !important; - opacity: .76 !important; -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { - opacity: 1 !important; -} - -/* Active tool/agent summaries render duplicated shimmer text with hard-coded - alpha and a white sweep. Keep the base label readable and tint only its - moving overlay with the skin accent; no ancestor opacity is changed. */ -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { - color: var(--ds-muted) !important; - -webkit-text-fill-color: var(--ds-muted) !important; -} - -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { - color: var(--ds-accent) !important; - -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -337,10 +130,12 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; + --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; + --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -423,9 +218,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] aside.app-shell-left-panel { background: linear-gradient(180deg, - var(--ds-sidebar), - rgb(var(--ds-bg-rgb) / .975)) !important; - border: 1px solid var(--ds-border) !important; + rgb(var(--ds-panel-rgb) / .98), + rgb(var(--ds-bg-rgb) / .96)) !important; + border: 1px solid var(--ds-line) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -435,53 +230,12 @@ html[data-dream-skin="active"] aside.app-shell-left-panel { html[data-dream-skin="active"] aside.app-shell-left-panel nav { background: transparent !important; } -/* Recent and other semantic lists may read as quiet raised sections. The - primary navigation stays on the native flat canvas: wrapping its split - fixed/scrolling DOM in a faux card creates the broken right edge called out - in the screenshot and relies on brittle nth-child geometry. */ -html[data-dream-skin="active"] aside.app-shell-left-panel - nav section:has([role="list"]) { - box-sizing: border-box !important; - margin-inline: 8px !important; - padding: 7px 6px 8px !important; - border: 1px solid var(--ds-zone-border) !important; - border-radius: 14px !important; - background: var(--ds-zone-surface-raised) !important; - box-shadow: var(--ds-zone-shadow) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel - nav section:has([role="list"]) > div > div:first-child { - margin-bottom: 3px !important; - padding-bottom: 5px !important; - border-bottom: 1px solid var(--ds-zone-divider) !important; -} - -/* Use the existing 16px native resize target as the visual boundary. The line - is non-interactive and never narrows or covers the resizer hit area. */ -html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]::before { - content: "" !important; - position: absolute !important; - top: 0 !important; - bottom: 0 !important; - left: 8px !important; - width: 1px !important; - background: var(--ds-zone-border) !important; - box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]:hover::before { - background: rgb(var(--ds-accent-rgb) / .52) !important; -} - html[data-dream-skin="active"] aside.app-shell-left-panel button, html[data-dream-skin="active"] aside.app-shell-left-panel a { color: var(--ds-text) !important; - transition: background-color var(--ds-motion-standard) var(--ds-ease-out), - border-color var(--ds-motion-standard) var(--ds-ease-out), - color var(--ds-motion-fast) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; + transition: background-color .18s cubic-bezier(.22, 1, .36, 1), + border-color .18s cubic-bezier(.22, 1, .36, 1), + color .18s cubic-bezier(.22, 1, .36, 1) !important; } html[data-dream-skin="active"] aside.app-shell-left-panel [class*="text-token-foreground"] { @@ -497,9 +251,8 @@ html[data-dream-skin="active"] aside.app-shell-left-panel svg { } html[data-dream-skin="active"] aside.app-shell-left-panel button:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel a:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:hover { - background: var(--ds-hover) !important; +html[data-dream-skin="active"] aside.app-shell-left-panel a:hover { + background: rgb(var(--ds-accent-rgb) / .09) !important; color: var(--ds-text) !important; transform: none !important; } @@ -526,23 +279,9 @@ html[data-dream-skin="active"] aside.app-shell-left-panel button[aria-label^=" html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-accent) !important; - border-radius: 9px 9px 10px 10px !important; - box-shadow: - 0 4px 14px rgb(var(--ds-bg-rgb) / .14), - inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; - color: var(--ds-text) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:active, -html[data-dream-skin="active"] aside.app-shell-left-panel button:active, -html[data-dream-skin="active"] aside.app-shell-left-panel a:active { - background: var(--ds-pressed) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] [class*="text-token"], -html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"] [class*="text-token"] { + background: rgb(var(--ds-accent-rgb) / .12) !important; + border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; + box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; color: var(--ds-text) !important; } @@ -550,18 +289,6 @@ html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] color: var(--ds-accent) !important; } -/* Onboarding and task suggestion cards are native sidebar surfaces, not - dialogs. When Codex itself is dark they otherwise remain charcoal beneath a - light skin. Keep them opaque enough for copy while preserving a soft lift. */ -html[data-dream-skin="active"][data-dream-shell="light"] aside.app-shell-left-panel - [class~="bg-token-main-surface-primary/70"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-surface) !important; - color: var(--ds-text) !important; - backdrop-filter: blur(14px) saturate(104%) !important; -} - html[data-dream-skin="active"] main.main-surface { position: relative !important; isolation: isolate; @@ -684,11 +411,10 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background: var(--reading-veil) !important; + background: linear-gradient(90deg, + var(--ds-task-immersive-edge), + var(--ds-task-immersive-mid) 64%, + var(--ds-task-immersive-far)) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -699,29 +425,21 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } -/* The artwork is painted once on the fixed body layer. Do not create a second - right-side image window: its own width, mask and vertical bounds form a - visible page seam and can bisect the sticky composer region. */ -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"]))::after { - content: none !important; - display: none !important; -} - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(14px) saturate(102%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface > header.app-header-tint { - background: var(--composer-surface) !important; - border: 0 !important; + background: transparent !important; + border-bottom: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -736,7 +454,9 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] main.main-surface:not(:has([role="main"])) [class*="_markdown"] { - text-shadow: none; + text-shadow: + 0 1px 2px rgb(var(--ds-panel-rgb) / .92), + 0 0 10px rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -773,26 +493,6 @@ html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) [role= 0 0 10px rgb(var(--ds-bg-rgb) / .46); } -/* Non-immersive task routes keep a conservative reading surface. Wide ambient - routes clear this local layer below because their veil already lives on the - complete main canvas, including the composer zone. */ -html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) - .thread-scroll-container { - background: - linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - var(--ds-content-reading-plane-far) 100%) !important; - backdrop-filter: none !important; -} - -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"])) - .thread-scroll-container { - background: transparent !important; -} - html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) [role="main"] { text-shadow: none; } @@ -810,19 +510,19 @@ html[data-dream-skin="active"] main.main-surface html[data-dream-skin="active"] main.main-surface div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: var(--ds-content-reading-plane) !important; + background: rgb(var(--ds-panel-rgb) / .62) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .44); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(14px) saturate(108%); + backdrop-filter: blur(7px) saturate(105%); } html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) article { - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -832,25 +532,70 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve the native flex layout, baselines and hit targets. A solid surface - is enough; a theme border/shadow becomes a full-width false divider. */ - background: var(--composer-surface) !important; - border: 0 !important; - box-shadow: none !important; - backdrop-filter: none !important; + /* Preserve Codex's native fixed header geometry and side-panel controls. */ + background: rgb(var(--ds-panel-rgb) / .90) !important; + border-bottom: 1px solid var(--ds-line) !important; + backdrop-filter: blur(14px) saturate(108%) !important; } -/* Native titlebar controls own the complete toolbar grid and hit targets. - Theme identity comes from the artwork and palette, so task chrome carries no - absolute-positioned labels and the home composer has no floating quote. */ +/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { + content: var(--dream-skin-name, "Codex Dream Skin") " · " + var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); + position: absolute; + left: 22px; + top: 4px; + z-index: 2; + max-width: min(42%, 360px); + overflow: hidden; + color: var(--ds-accent); + font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .03em; + text-overflow: ellipsis; + text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { + content: var(--dream-skin-status, "DREAM SKIN ONLINE"); + position: absolute; + right: 84px; + top: 13px; + z-index: 2; + max-width: 28%; + overflow: hidden; + color: var(--ds-muted); + font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .08em; + text-overflow: ellipsis; + text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: none !important; - display: none !important; + content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); + position: absolute; + right: 28px; + bottom: 72px; + z-index: 2; + pointer-events: none; + color: rgb(var(--ds-accent-rgb) / .74); + font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; + letter-spacing: 0; + text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); + transform: rotate(-3deg); +} + +html[data-dream-skin="active"][data-dream-shell="light"] + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, +html[data-dream-skin="active"][data-dream-shell="light"] + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { + text-shadow: none; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -1144,81 +889,15 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { - position: relative !important; - isolation: isolate !important; - z-index: 4 !important; overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background-clip: padding-box !important; - border: 1px solid var(--ds-border) !important; - border-width: 1px !important; - border-style: solid !important; - border-color: var(--ds-border) !important; + border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; border-radius: 22px !important; - background: var(--composer-surface) !important; - box-shadow: var(--ds-shadow-surface) !important; - backdrop-filter: blur(20px) saturate(108%) !important; - transition: - border-color var(--ds-motion-standard) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out), - background-color var(--ds-motion-standard) var(--ds-ease-out) !important; -} - -/* Current Codex builds force the native composer border width to zero from a - higher-priority cascade layer. Draw the semantic 1px boundary separately so - it remains stable without changing content opacity or input geometry. */ -html[data-dream-skin="active"] .composer-surface-chrome::before { - content: "" !important; - position: absolute !important; - inset: 0 !important; - z-index: 2 !important; - border: 1px solid var(--ds-border) !important; - border-radius: inherit !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:hover { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:hover::before { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:focus-within { - border-color: var(--ds-focus) !important; - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; - box-shadow: var(--ds-shadow-elevated) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:focus-within::before { - border-color: var(--ds-focus) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled), -html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"] { - color: var(--ds-text-muted) !important; - background: var(--ds-input-disabled) !important; - border-color: var(--ds-border) !important; - box-shadow: none !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled)::before, -html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"]::before { - border-color: var(--ds-border) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"]) { - border-color: var(--ds-danger) !important; - outline-color: var(--ds-danger) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; + box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; + backdrop-filter: blur(16px) saturate(108%) !important; } -html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"])::before { - border-color: var(--ds-danger) !important; -} +html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -1301,12 +980,13 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -1315,12 +995,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] .composer-surface-chrome p.placeholder::after { - color: var(--ds-text-secondary) !important; + color: rgb(var(--ds-muted-rgb) / .78) !important; opacity: 1 !important; } @@ -1333,7 +1013,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: var(--composer-surface) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1343,7 +1023,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"]) .composer-surface-chrome { - border: 1px solid var(--ds-border) !important; + border: 0 !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1353,12 +1033,12 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has( } html[data-dream-skin="active"][data-dream-art-wide="true"] [class*="_homeUtilityBar_"] { - background: var(--composer-surface) !important; + background: var(--ds-immersive-composer-solid) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1376,7 +1056,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"] [class*="_homeUtilityBar_"] button, @@ -1401,55 +1081,7 @@ html[data-dream-skin="active"] .composer-surface-chrome button:not([class~="bg-t } html[data-dream-skin="active"] .composer-surface-chrome p.placeholder::after { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -html[data-dream-skin="active"] :is(input, textarea)::placeholder { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -/* Menus, popovers and dialogs are independent elevated surfaces. Background - content may inform their material tint but must never remain legible through - them. */ -html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), -html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { - color: var(--ds-text-primary) !important; - background: var(--ds-popover) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-elevated) !important; - backdrop-filter: blur(18px) saturate(102%) !important; -} - -html[data-dream-skin="active"] :is( - button, - a, - input, - textarea, - [role="button"], - [role="menuitem"], - [tabindex] -):focus-visible { - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; -} - -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -):disabled, -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -)[aria-disabled="true"] { - color: var(--ds-text-muted) !important; + color: rgb(var(--ds-muted-rgb) / .82) !important; opacity: 1 !important; } @@ -1541,26 +1173,12 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { - background: var(--ds-highlight) !important; -} - -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { - transform: translateY(1px); - box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; -} - html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { - html[data-dream-skin="active"] { - --ambient-start: 52%; - } html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { content: none; } - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"]))::after { content: none; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/macos/assets/renderer-inject.js b/macos/assets/renderer-inject.js index 9ef10592..be799888 100644 --- a/macos/assets/renderer-inject.js +++ b/macos/assets/renderer-inject.js @@ -311,9 +311,11 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - // Header and lower-chrome slogans were retired because they compete with - // native toolbar controls and the composer. Keep the old property names in - // THEME_VARIABLES so reinjection removes values left by earlier versions. + setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); + setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( + THEME.brandSubtitle || "CODEX DREAM SKIN", + )); + setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/macos/assets/selectors.json b/macos/assets/selectors.json index ea0f0994..06e22231 100644 --- a/macos/assets/selectors.json +++ b/macos/assets/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/macos/package.json b/macos/package.json index b992b814..f8ebed43 100644 --- a/macos/package.json +++ b/macos/package.json @@ -1,6 +1,6 @@ { "name": "codex-dream-skin-studio", - "version": "1.5.6", + "version": "1.5.5", "private": true, "type": "module", "scripts": { diff --git a/macos/scripts/common-macos.sh b/macos/scripts/common-macos.sh index 921ae633..32a58da2 100755 --- a/macos/scripts/common-macos.sh +++ b/macos/scripts/common-macos.sh @@ -29,7 +29,7 @@ CODEX_APP_JOB_LABEL="com.openai.codex-dream-skin-studio.app" INJECTOR_JOB_LABEL="com.openai.codex-dream-skin-studio.injector" EXPECTED_CODEX_TEAM_ID="2DC432GLL2" EXPECTED_CODEX_REQUIREMENT="anchor apple generic and certificate leaf[subject.OU] = \"$EXPECTED_CODEX_TEAM_ID\"" -SKIN_VERSION="1.5.6" +SKIN_VERSION="1.5.5" DREAM_SKIN_VALIDATED_RUNTIME_PID="" DREAM_SKIN_VALIDATED_RUNTIME_BUNDLE="" DREAM_SKIN_VALIDATED_RUNTIME_EXE="" diff --git a/macos/scripts/injector.mjs b/macos/scripts/injector.mjs index 871420b1..924fe814 100644 --- a/macos/scripts/injector.mjs +++ b/macos/scripts/injector.mjs @@ -42,7 +42,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.6"; +const SKIN_VERSION = "1.5.5"; const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]); const CDP_ID_PATTERN = /^[A-Za-z0-9._-]{1,200}$/; const MAX_ART_BYTES = 10 * 1024 * 1024; @@ -232,18 +232,13 @@ export function assessRendererVerification(renderer, nativeWindow, expected) { const structurePass = settingsRoute ? Boolean(result.settings?.visible) : Boolean(result.shell?.visible) && Boolean(result.sidebar?.visible); - const threadScrollPass = !result.threadSurface || ( - result.threadSurface.visible - && ["auto", "scroll", "overlay"].includes(result.threadSurface.overflowY) - && result.threadSurface.pointerEvents !== "none" - ); const nativeWindowPass = nativeWindow?.status === "ready"; const fallbackWindowPass = nativeWindow?.status === "unsupported"; const windowPass = documentVisible && viewportPass && (nativeWindowPass || fallbackWindowPass); const basePass = result.installed && result.version === expected.skinVersion && result.stylePresent && result.businessClassPollution === 0 - && structurePass && threadScrollPass && windowPass && !result.documentOverflow?.x; + && structurePass && windowPass && !result.documentOverflow?.x; const payloadPass = (!expected.expectedThemeId || result.themeId === expected.expectedThemeId) && (!expected.expectedRevision || result.revision === expected.expectedRevision); const visibleSuggestionLabels = Array.isArray(result.suggestionLabels) @@ -263,7 +258,6 @@ export function assessRendererVerification(renderer, nativeWindow, expected) { nativeWindowPass, payloadPass, structurePass, - threadScrollPass, viewportPass, windowPass, }; @@ -1109,14 +1103,6 @@ async function verifySession(session, expectedThemeId = null, expectedRevision = const shell = box(document.querySelector(${selectorLiteral("shell-main")})); const composer = box(document.querySelector(${selectorLiteral("composer-chrome")})); const sidebar = box(document.querySelector(${selectorLiteral("left-panel")})); - const threadSurfaceNode = document.querySelector(${selectorLiteral("thread-surface")}); - const threadSurfaceStyle = threadSurfaceNode ? getComputedStyle(threadSurfaceNode) : null; - const threadSurface = threadSurfaceNode ? { - ...box(threadSurfaceNode), - overflowX: threadSurfaceStyle.overflowX, - overflowY: threadSurfaceStyle.overflowY, - pointerEvents: threadSurfaceStyle.pointerEvents, - } : null; const settingsBoxes = [ box(document.querySelector(${selectorLiteral("appearance-radio")})), box(document.querySelector(${stableTestidLiteral("theme-preview")})), @@ -1151,7 +1137,6 @@ async function verifySession(session, expectedThemeId = null, expectedRevision = shell, composer, sidebar, - threadSurface, settings, viewport: { width: innerWidth, height: innerHeight }, documentOverflow: { diff --git a/macos/tests/run-tests.sh b/macos/tests/run-tests.sh index bfee73ff..ca068e6d 100755 --- a/macos/tests/run-tests.sh +++ b/macos/tests/run-tests.sh @@ -82,7 +82,7 @@ UPDATE_JSON="$({ })" "$NODE" -e ' const value = JSON.parse(process.argv[1]); - if (value.currentVersion !== "v1.5.6" || value.latestVersion !== "v9.8.7") process.exit(1); + if (value.currentVersion !== "v1.5.5" || value.latestVersion !== "v9.8.7") process.exit(1); if (!value.updateAvailable) process.exit(1); if (value.releaseUrl !== "https://github.com/Fei-Away/Codex-Dream-Skin/releases/latest") process.exit(1); ' "$UPDATE_JSON" @@ -1063,7 +1063,7 @@ CRLF_BACKUP="$TMP/config-crlf-backup.json" "$NODE" "$ROOT/scripts/theme-config.mjs" restore "$CRLF_CONFIG" "$CRLF_BACKUP" >/dev/null /usr/bin/cmp -s "$CRLF_CONFIG" "$TMP/original-crlf.toml" -/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.6" ]' _ "$ROOT" +/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.5" ]' _ "$ROOT" if [ "${CODEX_DREAM_SKIN_SKIP_DOCTOR:-0}" = "1" ]; then printf 'SKIP: Doctor requires an installed, signed Codex app.\n' DOCTOR_RESULT="skipped" diff --git a/macos/tests/window-readiness.test.mjs b/macos/tests/window-readiness.test.mjs index c97de454..bc11a6d1 100644 --- a/macos/tests/window-readiness.test.mjs +++ b/macos/tests/window-readiness.test.mjs @@ -42,14 +42,6 @@ const baseRenderer = { scope: { level: "L1", baseState: "thread" }, shell: { visible: true, width: 900, height: 740 }, sidebar: { visible: true, width: 280, height: 740 }, - threadSurface: { - visible: true, - width: 900, - height: 700, - overflowX: "hidden", - overflowY: "auto", - pointerEvents: "auto", - }, settings: null, homeRoute: false, homePresent: false, @@ -63,20 +55,6 @@ const baseRenderer = { assert.equal(readyNativeWindow.status, "ready"); assert.equal(assessRendererVerification(baseRenderer, readyNativeWindow, exactPayload).pass, true); -const brokenThreadScroll = { - ...baseRenderer, - threadSurface: { ...baseRenderer.threadSurface, overflowY: "visible" }, -}; -assert.equal( - assessRendererVerification(brokenThreadScroll, readyNativeWindow, exactPayload).pass, - false, - "A skin that replaces the native task scroller with visible overflow must fail verification.", -); -assert.equal( - assessRendererVerification(brokenThreadScroll, readyNativeWindow, exactPayload) - .checks.threadScrollPass, - false, -); const windowCalls = []; assert.equal((await inspectNativeWindow({ diff --git a/runtime/dream-skin.css b/runtime/dream-skin.css index 5b865196..b2559ffc 100644 --- a/runtime/dream-skin.css +++ b/runtime/dream-skin.css @@ -59,70 +59,6 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); - /* One-canvas layout contract. Native window chrome and content geometry - remain authoritative; artwork must adapt to the product, never reserve - layout space at the expense of typing or reading comfort. */ - --titlebar-safe-left: max(env(safe-area-inset-left), 0px); - --titlebar-safe-right: max(env(safe-area-inset-right), 0px); - --ambient-start: 52%; - --ambient-feather: clamp(240px, 24vw, 320px); - /* Semantic design tokens. Theme packages provide the primitive palette; - native components consume these roles so material, state and accessibility - decisions stay consistent instead of scattering alpha values. */ - --ds-canvas: var(--ds-bg); - --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); - --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); - --reading-veil: linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), - rgb(var(--ds-panel-rgb) / .06) 100%); - --ds-surface: var(--ds-panel); - --message-surface: rgb(var(--ds-panel-rgb) / .94); - --ds-elevated-surface: var(--ds-panel); - --ds-popover: rgb(var(--ds-panel-rgb) / .988); - --ds-input: rgb(var(--ds-panel-rgb) / .985); - --composer-surface: rgb(var(--ds-panel-rgb) / .92); - --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); - --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); - --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); - --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); - --ds-focus: var(--ds-accent); - --ds-border: var(--ds-line); - --ds-text-primary: var(--ds-text); - --ds-text-secondary: var(--ds-muted); - --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); - --ds-danger: #ff9a8f; - --ds-danger-surface: #4b1f22; - --ds-danger-foreground: #fff0ed; - --ds-danger-border: #d87970; - --ds-success: #80c79b; - --ds-success-surface: #203d2d; - --ds-success-foreground: #e6f4e9; - --ds-success-border: #72a782; - --ds-warning-surface: #4c351c; - --ds-warning-foreground: #fff0cf; - --ds-warning-border: #cf984b; - --ds-info-surface: #223842; - --ds-info-foreground: #edf7fa; - --ds-info-border: #7899a6; - --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); - --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); - --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); - --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); - --ds-zone-shadow: - 0 7px 18px rgb(var(--ds-bg-rgb) / .09), - inset 0 1px rgb(var(--ds-panel-rgb) / .82); - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); - --ds-shadow-elevated: - 0 16px 38px rgb(var(--ds-bg-rgb) / .20), - 0 2px 8px rgb(var(--ds-bg-rgb) / .12); - --ds-motion-fast: 120ms; - --ds-motion-standard: 160ms; - --ds-motion-reveal: 210ms; - --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -171,24 +107,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); - --ds-danger: #9c3d3a; - --ds-danger-surface: #f8e6e3; - --ds-danger-foreground: #6f2523; - --ds-danger-border: #b85650; - --ds-success: #45624d; - --ds-success-surface: #e4efe5; - --ds-success-foreground: #28543a; - --ds-success-border: #5f856b; - --ds-warning-surface: #f8edd7; - --ds-warning-foreground: #674515; - --ds-warning-border: #aa7a32; - --ds-info-surface: #e8eef1; - --ds-info-foreground: #284754; - --ds-info-border: #6f8b97; - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); - --ds-shadow-elevated: - 0 18px 42px rgb(var(--ds-bg-rgb) / .16), - 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -197,131 +115,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); - --vscode-inputValidation-errorBackground: var(--ds-danger-surface); - --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); - --vscode-inputValidation-errorBorder: var(--ds-danger-border); - --color-token-input-validation-error-background: var(--ds-danger-surface); - --color-token-input-validation-error-foreground: var(--ds-danger-foreground); - --color-token-input-validation-error-border: var(--ds-danger-border); - --color-token-error-foreground: var(--ds-danger-foreground); - --color-token-editor-error-foreground: var(--ds-danger-foreground); -} - -/* Codex can remain electron-dark while a theme explicitly requests a light - shell. Bridge the native token family to the active skin so task cards, - inputs, secondary copy and icons do not keep dark-mode values on a light - wallpaper. This is component-scoped token remapping, never whole-page - opacity. */ -html[data-dream-skin="active"][data-dream-shell="light"] { - --vscode-foreground: var(--ds-text); - --vscode-descriptionForeground: var(--ds-muted); - --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); - --color-background-panel: var(--ds-panel); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); - --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); - --color-token-conversation-body: var(--ds-muted); - --color-token-conversation-summary-leading: var(--ds-muted); - --color-token-conversation-summary-trailing: var(--ds-muted); - --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); - --color-token-description-foreground: var(--ds-muted); - --color-token-list-hover-background: var(--ds-hover); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* The scheduled-task detail side panel uses a separate inline panel surface - variable and input token family. Reinforce the light-shell bridge at its - native form boundary so the prompt, detail and schedule cards cannot retain - electron-dark surfaces or pale input copy. */ -html[data-dream-skin="active"][data-dream-shell="light"] - [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { - color: var(--ds-text); - --color-background-panel: var(--ds-input); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: var(--ds-muted); - --color-token-description-foreground: var(--ds-muted); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* Native toasts use one generic foreground token across four different - surfaces. Resolve the actual host state classes into independent semantic - palettes before styling the shared alert component. */ -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-dropdown-background"] { - --ds-alert-surface: var(--ds-info-surface); - --ds-alert-foreground: var(--ds-info-foreground); - --ds-alert-border: var(--ds-info-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { - --ds-alert-surface: var(--ds-success-surface); - --ds-alert-foreground: var(--ds-success-foreground); - --ds-alert-border: var(--ds-success-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { - --ds-alert-surface: var(--ds-warning-surface); - --ds-alert-foreground: var(--ds-warning-foreground); - --ds-alert-border: var(--ds-warning-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { - --ds-alert-surface: var(--ds-danger-surface); - --ds-alert-foreground: var(--ds-danger-foreground); - --ds-alert-border: var(--ds-danger-border); -} - -html[data-dream-skin="active"] [role="alert"].alert-root { - background-color: var(--ds-alert-surface, var(--ds-popover)) !important; - border-color: var(--ds-alert-border, var(--ds-border)) !important; - color: var(--ds-alert-foreground, var(--ds-text)) !important; - --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { - color: var(--ds-alert-foreground, var(--ds-text)) !important; - opacity: .76 !important; -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { - opacity: 1 !important; -} - -/* Active tool/agent summaries render duplicated shimmer text with hard-coded - alpha and a white sweep. Keep the base label readable and tint only its - moving overlay with the skin accent; no ancestor opacity is changed. */ -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { - color: var(--ds-muted) !important; - -webkit-text-fill-color: var(--ds-muted) !important; -} - -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { - color: var(--ds-accent) !important; - -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -337,10 +130,12 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; + --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; + --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -423,9 +218,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ { background: linear-gradient(180deg, - var(--ds-sidebar), - rgb(var(--ds-bg-rgb) / .975)) !important; - border: 1px solid var(--ds-border) !important; + rgb(var(--ds-panel-rgb) / .98), + rgb(var(--ds-bg-rgb) / .96)) !important; + border: 1px solid var(--ds-line) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -435,53 +230,12 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ { html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ nav { background: transparent !important; } -/* Recent and other semantic lists may read as quiet raised sections. The - primary navigation stays on the native flat canvas: wrapping its split - fixed/scrolling DOM in a faux card creates the broken right edge called out - in the screenshot and relies on brittle nth-child geometry. */ -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ - nav section:has([role="list"]) { - box-sizing: border-box !important; - margin-inline: 8px !important; - padding: 7px 6px 8px !important; - border: 1px solid var(--ds-zone-border) !important; - border-radius: 14px !important; - background: var(--ds-zone-surface-raised) !important; - box-shadow: var(--ds-zone-shadow) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ - nav section:has([role="list"]) > div > div:first-child { - margin-bottom: 3px !important; - padding-bottom: 5px !important; - border-bottom: 1px solid var(--ds-zone-divider) !important; -} - -/* Use the existing 16px native resize target as the visual boundary. The line - is non-interactive and never narrows or covers the resizer hit area. */ -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ > [role="separator"]::before { - content: "" !important; - position: absolute !important; - top: 0 !important; - bottom: 0 !important; - left: 8px !important; - width: 1px !important; - background: var(--ds-zone-border) !important; - box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ > [role="separator"]:hover::before { - background: rgb(var(--ds-accent-rgb) / .52) !important; -} - html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button, html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a { color: var(--ds-text) !important; - transition: background-color var(--ds-motion-standard) var(--ds-ease-out), - border-color var(--ds-motion-standard) var(--ds-ease-out), - color var(--ds-motion-fast) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; + transition: background-color .18s cubic-bezier(.22, 1, .36, 1), + border-color .18s cubic-bezier(.22, 1, .36, 1), + color .18s cubic-bezier(.22, 1, .36, 1) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class*="text-token-foreground"] { @@ -497,9 +251,8 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ svg { } html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button:hover, -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:hover, -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [role="button"]:hover { - background: var(--ds-hover) !important; +html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:hover { + background: rgb(var(--ds-accent-rgb) / .09) !important; color: var(--ds-text) !important; transform: none !important; } @@ -526,23 +279,9 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button[aria-label^= html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-accent) !important; - border-radius: 9px 9px 10px 10px !important; - box-shadow: - 0 4px 14px rgb(var(--ds-bg-rgb) / .14), - inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; - color: var(--ds-text) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [role="button"]:active, -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ button:active, -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ a:active { - background: var(--ds-pressed) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page"] [class*="text-token"], -html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [class~="bg-token-list-hover-background"] [class*="text-token"] { + background: rgb(var(--ds-accent-rgb) / .12) !important; + border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; + box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; color: var(--ds-text) !important; } @@ -550,18 +289,6 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_LEFT_PANEL__ [aria-current="page color: var(--ds-accent) !important; } -/* Onboarding and task suggestion cards are native sidebar surfaces, not - dialogs. When Codex itself is dark they otherwise remain charcoal beneath a - light skin. Keep them opaque enough for copy while preserving a soft lift. */ -html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_LEFT_PANEL__ - [class~="bg-token-main-surface-primary/70"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-surface) !important; - color: var(--ds-text) !important; - backdrop-filter: blur(14px) saturate(104%) !important; -} - html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ { position: relative !important; isolation: isolate; @@ -684,11 +411,10 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) { - overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background: var(--reading-veil) !important; + background: linear-gradient(90deg, + var(--ds-task-immersive-edge), + var(--ds-task-immersive-mid) 64%, + var(--ds-task-immersive-far)) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -699,29 +425,21 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } -/* The artwork is painted once on the fixed body layer. Do not create a second - right-side image window: its own width, mask and vertical bounds form a - visible page seam and can bisect the sticky composer region. */ -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__))::after { - content: none !important; - display: none !important; -} - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(14px) saturate(102%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_SHELL_MAIN__ > __DREAM_SELECTOR_HEADER_TINT__ { - background: var(--composer-surface) !important; - border: 0 !important; + background: transparent !important; + border-bottom: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -736,7 +454,9 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_MARKDOWN__ { - text-shadow: none; + text-shadow: + 0 1px 2px rgb(var(--ds-panel-rgb) / .92), + 0 0 10px rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -773,26 +493,6 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SE 0 0 10px rgb(var(--ds-bg-rgb) / .46); } -/* Non-immersive task routes keep a conservative reading surface. Wide ambient - routes clear this local layer below because their veil already lives on the - complete main canvas, including the composer zone. */ -html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) - .thread-scroll-container { - background: - linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - var(--ds-content-reading-plane-far) 100%) !important; - backdrop-filter: none !important; -} - -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) - .thread-scroll-container { - background: transparent !important; -} - html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) [role="main"] { text-shadow: none; } @@ -810,19 +510,19 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: var(--ds-content-reading-plane) !important; + background: rgb(var(--ds-panel-rgb) / .62) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .44); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(14px) saturate(108%); + backdrop-filter: blur(7px) saturate(105%); } html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) article { - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -832,25 +532,70 @@ html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_HOME_R } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ > __DREAM_SELECTOR_HEADER_TINT__ { - /* Preserve the native flex layout, baselines and hit targets. A solid surface - is enough; a theme border/shadow becomes a full-width false divider. */ - background: var(--composer-surface) !important; - border: 0 !important; - box-shadow: none !important; - backdrop-filter: none !important; + /* Preserve Codex's native fixed header geometry and side-panel controls. */ + background: rgb(var(--ds-panel-rgb) / .90) !important; + border-bottom: 1px solid var(--ds-line) !important; + backdrop-filter: blur(14px) saturate(108%) !important; } -/* Native titlebar controls own the complete toolbar grid and hit targets. - Theme identity comes from the artwork and palette, so task chrome carries no - absolute-positioned labels and the home composer has no floating quote. */ +/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before { + content: var(--dream-skin-name, "Codex Dream Skin") " · " + var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); + position: absolute; + left: 22px; + top: 4px; + z-index: 2; + max-width: min(42%, 360px); + overflow: hidden; + color: var(--ds-accent); + font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .03em; + text-overflow: ellipsis; + text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after, + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { + content: var(--dream-skin-status, "DREAM SKIN ONLINE"); + position: absolute; + right: 84px; + top: 13px; + z-index: 2; + max-width: 28%; + overflow: hidden; + color: var(--ds-muted); + font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .08em; + text-overflow: ellipsis; + text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)::after { - content: none !important; - display: none !important; + content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); + position: absolute; + right: 28px; + bottom: 72px; + z-index: 2; + pointer-events: none; + color: rgb(var(--ds-accent-rgb) / .74); + font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; + letter-spacing: 0; + text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); + transform: rotate(-3deg); +} + +html[data-dream-skin="active"][data-dream-shell="light"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, +html[data-dream-skin="active"][data-dream-shell="light"] + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { + text-shadow: none; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -1144,81 +889,15 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HO html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HOME_SUGGESTIONS__ button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ { - position: relative !important; - isolation: isolate !important; - z-index: 4 !important; overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background-clip: padding-box !important; - border: 1px solid var(--ds-border) !important; - border-width: 1px !important; - border-style: solid !important; - border-color: var(--ds-border) !important; + border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; border-radius: 22px !important; - background: var(--composer-surface) !important; - box-shadow: var(--ds-shadow-surface) !important; - backdrop-filter: blur(20px) saturate(108%) !important; - transition: - border-color var(--ds-motion-standard) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out), - background-color var(--ds-motion-standard) var(--ds-ease-out) !important; -} - -/* Current Codex builds force the native composer border width to zero from a - higher-priority cascade layer. Draw the semantic 1px boundary separately so - it remains stable without changing content opacity or input geometry. */ -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { - content: "" !important; - position: absolute !important; - inset: 0 !important; - z-index: 2 !important; - border: 1px solid var(--ds-border) !important; - border-radius: inherit !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:hover { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:hover::before { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:focus-within { - border-color: var(--ds-focus) !important; - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; - box-shadow: var(--ds-shadow-elevated) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:focus-within::before { - border-color: var(--ds-focus) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has(:disabled), -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__[aria-disabled="true"] { - color: var(--ds-text-muted) !important; - background: var(--ds-input-disabled) !important; - border-color: var(--ds-border) !important; - box-shadow: none !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has(:disabled)::before, -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__[aria-disabled="true"]::before { - border-color: var(--ds-border) !important; -} - -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has([aria-invalid="true"]) { - border-color: var(--ds-danger) !important; - outline-color: var(--ds-danger) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; + box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; + backdrop-filter: blur(16px) saturate(108%) !important; } -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__:has([aria-invalid="true"])::before { - border-color: var(--ds-danger) !important; -} +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { content: none !important; } /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -1301,12 +980,13 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_SHEL } html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -1315,12 +995,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_COMPOSER_CHROME__ p.placeholder::after { - color: var(--ds-text-secondary) !important; + color: rgb(var(--ds-muted-rgb) / .78) !important; opacity: 1 !important; } @@ -1333,7 +1013,7 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: var(--composer-surface) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1343,7 +1023,7 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ { html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__:has(__DREAM_SELECTOR_HOME_UTILITY__) __DREAM_SELECTOR_COMPOSER_CHROME__ { - border: 1px solid var(--ds-border) !important; + border: 0 !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1353,12 +1033,12 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__:has(__DREAM_SELECTO } html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_HOME_UTILITY__ { - background: var(--composer-surface) !important; + background: var(--ds-immersive-composer-solid) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1376,7 +1056,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_UTILITY__ button, @@ -1401,55 +1081,7 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ button:not([cl } html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ p.placeholder::after { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -html[data-dream-skin="active"] :is(input, textarea)::placeholder { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -/* Menus, popovers and dialogs are independent elevated surfaces. Background - content may inform their material tint but must never remain legible through - them. */ -html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), -html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { - color: var(--ds-text-primary) !important; - background: var(--ds-popover) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-elevated) !important; - backdrop-filter: blur(18px) saturate(102%) !important; -} - -html[data-dream-skin="active"] :is( - button, - a, - input, - textarea, - [role="button"], - [role="menuitem"], - [tabindex] -):focus-visible { - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; -} - -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -):disabled, -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -)[aria-disabled="true"] { - color: var(--ds-text-muted) !important; + color: rgb(var(--ds-muted-rgb) / .82) !important; opacity: 1 !important; } @@ -1541,26 +1173,12 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { - background: var(--ds-highlight) !important; -} - -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { - transform: translateY(1px); - box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; -} - html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { - html[data-dream-skin="active"] { - --ambient-start: 52%; - } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)::after { content: none; } - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__))::after { content: none; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/runtime/renderer-inject.js b/runtime/renderer-inject.js index 49563f24..4066c711 100644 --- a/runtime/renderer-inject.js +++ b/runtime/renderer-inject.js @@ -311,9 +311,11 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - // Header and lower-chrome slogans were retired because they compete with - // native toolbar controls and the composer. Keep the old property names in - // THEME_VARIABLES so reinjection removes values left by earlier versions. + setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); + setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( + THEME.brandSubtitle || "CODEX DREAM SKIN", + )); + setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/tools/renderer-runtime.test.mjs b/tools/renderer-runtime.test.mjs index 6676d8a5..c0492567 100644 --- a/tools/renderer-runtime.test.mjs +++ b/tools/renderer-runtime.test.mjs @@ -3,25 +3,6 @@ import fs from "node:fs/promises"; import path from "node:path"; import vm from "node:vm"; -function contrastRatio(firstHex, secondHex) { - const luminance = (hex) => { - const channels = hex.match(/[0-9a-f]{2}/gi).map((value) => Number.parseInt(value, 16) / 255); - const linear = channels.map((value) => ( - value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4 - )); - return (0.2126 * linear[0]) + (0.7152 * linear[1]) + (0.0722 * linear[2]); - }; - const first = luminance(firstHex); - const second = luminance(secondHex); - return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05); -} - -function hexVariable(block, name) { - const match = block.match(new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, "i")); - assert.ok(match, `Expected ${name} to be a six-digit hex color.`); - return match[1]; -} - function styleDeclaration() { const values = new Map(); return { @@ -255,26 +236,6 @@ export async function runRendererRuntimeTest(assetRoot) { const template = await fs.readFile(path.join(assetRoot, "renderer-inject.js"), "utf8"); const css = await fs.readFile(path.join(assetRoot, "dream-skin.css"), "utf8"); fixture.template = template; - const darkThemeBlock = css.match(/:root\[data-dream-skin="active"\]\s*\{([\s\S]*?)\n\}/)?.[1]; - const lightThemeBlock = css.match(/html\[data-dream-skin="active"\]\[data-dream-shell="light"\]\s*\{([\s\S]*?)\n\}/)?.[1]; - assert.ok(darkThemeBlock, "Expected the active dark-shell semantic token block."); - assert.ok(lightThemeBlock, "Expected the active light-shell semantic token block."); - for (const role of ["danger", "success", "warning", "info"]) { - assert.ok( - contrastRatio( - hexVariable(darkThemeBlock, `--ds-${role}-foreground`), - hexVariable(darkThemeBlock, `--ds-${role}-surface`), - ) >= 4.5, - `Dark-shell ${role} alerts must meet WCAG AA text contrast.`, - ); - assert.ok( - contrastRatio( - hexVariable(lightThemeBlock, `--ds-${role}-foreground`), - hexVariable(lightThemeBlock, `--ds-${role}-surface`), - ) >= 4.5, - `Light-shell ${role} alerts must meet WCAG AA text contrast.`, - ); - } assert.match(template, /adoptedStyleSheets/); assert.match(template, /CSSStyleSheet/); @@ -297,108 +258,12 @@ export async function runRendererRuntimeTest(assetRoot) { assert.match(css, /main\.main-surface:has\(\[role="main"\]\)/); assert.match(css, /main\.main-surface:not\(:has\(\[role="main"\]\)\)/); assert.doesNotMatch(css, /:has\([^()]*:has\(/); - assert.doesNotMatch(css, - /\[aria-label="(?:隐藏边栏|Hide sidebar|返回|Back|前进|Forward)"\]/, - "Theme CSS must not resize or reposition native titlebar controls."); - assert.match(css, - /header\.app-header-tint::before,[\s\S]*?header\.app-header-tint::after,[\s\S]*?main\.main-surface:has\(\[role="main"\]\)::after\s*\{[\s\S]*?content:\s*none\s*!important[\s\S]*?display:\s*none\s*!important/, - "Task chrome and home composer areas must not carry positioned theme slogans."); - assert.doesNotMatch(css, - /content:\s*var\(--dream-skin-(?:chrome-mark|status|quote)/, - "Retired theme slogans must not be painted into native chrome."); + assert.match(css, /content:\s*var\(--dream-skin-name[\s\S]{0,180}var\(--dream-skin-brand-subtitle/); + assert.match(css, /content:\s*var\(--dream-skin-status/); + assert.match(css, /content:\s*var\(--dream-skin-quote/); assert.match(css, /--ds-task-full-veil/); assert.match(css, /data-dream-task-mode="full"/); assert.match(css, /background-image:\s*var\(--ds-task-full-veil\),\s*var\(--dream-skin-art\)/); - assert.match(css, - /data-dream-shell="light"\]\s*\{[\s\S]*?--vscode-foreground:\s*var\(--ds-text\)[\s\S]*?--color-background-panel:\s*var\(--ds-panel\)[\s\S]*?--color-token-bg-fog:\s*var\(--ds-panel-2\)[\s\S]*?--color-token-main-surface-primary:\s*var\(--ds-panel\)[\s\S]*?--color-token-conversation-body:\s*var\(--ds-muted\)[\s\S]*?--color-token-conversation-summary-trailing:\s*var\(--ds-muted\)[\s\S]*?--color-token-input-background:\s*var\(--ds-input\)[\s\S]*?--color-token-input-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-placeholder-foreground:\s*var\(--ds-muted\)[\s\S]*?--color-token-border:\s*var\(--ds-border\)/, - "A light skin must bridge native dark-shell text, panel, task-card, conversation, input and border tokens."); - assert.match(css, - /\[class~="bg-token-main-surface-primary"\]:has\(form\[id\^="automation-side-panel-form"\]\)\s*\{[\s\S]*?--color-background-panel:\s*var\(--ds-input\)[\s\S]*?--color-token-main-surface-primary:\s*var\(--ds-panel\)[\s\S]*?--color-token-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-foreground:\s*var\(--ds-text\)[\s\S]*?--color-token-input-placeholder-foreground:\s*var\(--ds-muted\)[\s\S]*?--color-token-border:\s*var\(--ds-border\)/, - "Scheduled-task side panels must not retain electron-dark cards or pale input text under a light skin."); - assert.match(css, - /\.loading-shimmer-pure-text\s*\{[\s\S]*?color:\s*var\(--ds-muted\)\s*!important[\s\S]*?-webkit-text-fill-color:\s*var\(--ds-muted\)\s*!important[\s\S]*?\.loading-shimmer-pure-text\s+\*\s*\{[\s\S]*?color:\s*var\(--ds-accent\)\s*!important[\s\S]*?-webkit-text-fill-color:\s*var\(--ds-accent\)\s*!important/, - "Active conversation summaries must keep readable theme-colored shimmer text."); - assert.match(css, - /\[aria-current="page"\]\s*\{[\s\S]*?background:\s*var\(--ds-selected\)[\s\S]*?border:\s*1px solid var\(--ds-accent\)[\s\S]*?box-shadow:[\s\S]*?inset 3px 0 rgb\(var\(--ds-accent-rgb\)/, - "Selected sidebar rows must use a readable surface plus a solid accent indicator."); - assert.match(css, - /\[class~="bg-token-main-surface-primary\/70"\]\s*\{[\s\S]*?background:\s*var\(--ds-selected\)[\s\S]*?color:\s*var\(--ds-text\)/, - "Native sidebar task cards must not retain the host dark surface under a light skin."); - assert.doesNotMatch(css, /aside\.app-shell-left-panel\s+nav\s*>\s*div(?::first-child|:nth-child)/, - "Primary navigation must keep native flow instead of brittle split-card geometry."); - assert.match(css, - /aside\.app-shell-left-panel[\s\S]*?nav section:has\(\[role="list"\]\)\s*\{[\s\S]*?margin-inline:\s*8px\s*!important[\s\S]*?border:\s*1px solid var\(--ds-zone-border\)[\s\S]*?background:\s*var\(--ds-zone-surface-raised\)/, - "Semantic sidebar lists must own distinct raised section surfaces."); - assert.match(css, - /aside\.app-shell-left-panel > \[role="separator"\]::before\s*\{[\s\S]*?left:\s*8px\s*!important[\s\S]*?width:\s*1px\s*!important[\s\S]*?pointer-events:\s*none\s*!important/, - "The native resize target must expose a clear non-interactive visual boundary."); - assert.match(css, - /--titlebar-safe-left:[\s\S]*?--titlebar-safe-right:[\s\S]*?--ambient-start:\s*52%[\s\S]*?--ambient-feather:\s*clamp\(240px,\s*24vw,\s*320px\)[\s\S]*?--ds-canvas:\s*var\(--ds-bg\)[\s\S]*?--ds-content-reading-plane:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.975\)[\s\S]*?--reading-veil:\s*linear-gradient\([\s\S]*?--message-surface:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.94\)[\s\S]*?--ds-popover:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.988\)[\s\S]*?--ds-input:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.985\)[\s\S]*?--composer-surface:\s*rgb\(var\(--ds-panel-rgb\)\s*\/\s*\.92\)[\s\S]*?--ds-selected:[\s\S]*?--ds-hover:[\s\S]*?--ds-pressed:[\s\S]*?--ds-focus:[\s\S]*?--ds-danger:[\s\S]*?--ds-danger-surface:[\s\S]*?--ds-danger-foreground:[\s\S]*?--ds-danger-border:[\s\S]*?--ds-success:[\s\S]*?--ds-success-surface:[\s\S]*?--ds-success-foreground:[\s\S]*?--ds-success-border:[\s\S]*?--ds-warning-surface:[\s\S]*?--ds-warning-foreground:[\s\S]*?--ds-warning-border:[\s\S]*?--ds-info-surface:[\s\S]*?--ds-info-foreground:[\s\S]*?--ds-info-border:[\s\S]*?--ds-zone-surface:[\s\S]*?--ds-zone-surface-raised:[\s\S]*?--ds-zone-border:[\s\S]*?--ds-zone-divider:/, - "The renderer must expose a complete semantic token layer for surfaces, states and status colors."); - assert.match(css, - /--vscode-inputValidation-errorBackground:\s*var\(--ds-danger-surface\)[\s\S]*?--vscode-inputValidation-errorForeground:\s*var\(--ds-danger-foreground\)[\s\S]*?--vscode-inputValidation-errorBorder:\s*var\(--ds-danger-border\)[\s\S]*?--color-token-input-validation-error-background:\s*var\(--ds-danger-surface\)[\s\S]*?--color-token-input-validation-error-foreground:\s*var\(--ds-danger-foreground\)[\s\S]*?--color-token-input-validation-error-border:\s*var\(--ds-danger-border\)/, - "Native validation tokens must resolve to the skin's semantic error palette."); - assert.match(css, - /\[role="alert"\]\.alert-root\[class~="bg-token-dropdown-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-info-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-info-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-success-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-warning-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-warning-surface\)[\s\S]*?\[role="alert"\]\.alert-root\[class~="bg-token-input-validation-error-background"\]\s*\{[\s\S]*?--ds-alert-surface:\s*var\(--ds-danger-surface\)/, - "Every native toast level must resolve to its own semantic alert palette."); - assert.match(css, - /\[role="alert"\]\.alert-root\s*\{[\s\S]*?background-color:\s*var\(--ds-alert-surface,\s*var\(--ds-popover\)\)\s*!important[\s\S]*?border-color:\s*var\(--ds-alert-border,\s*var\(--ds-border\)\)\s*!important[\s\S]*?color:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)\s*!important[\s\S]*?--color-token-text-secondary:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)/, - "Native alerts must carry readable semantic surface, border and inherited foreground roles."); - assert.match(css, - /\[role="alert"\]\.alert-root > button\[aria-label\]\s*\{[\s\S]*?color:\s*var\(--ds-alert-foreground,\s*var\(--ds-text\)\)\s*!important[\s\S]*?opacity:\s*\.76\s*!important/, - "Alert close controls must remain visibly distinct without changing their native hit target."); - assert.doesNotMatch(css, - /--ds-art-clear-rail|padding-inline-(?:start|end):\s*var\(--ds-art-clear-rail\)/, - "Artwork must adapt to the application without reserving or shrinking native content geometry."); - assert.doesNotMatch(css, - /data-dream-art-safe(?:-area)?="(?:left|right)"[^{]*\{[^}]*--ds-art-position:/, - "Safe-side atmosphere metadata must not override the theme's explicit artwork focus point."); - assert.match(css, - /@media \(max-width:\s*1120px\)[\s\S]*?--ambient-start:\s*52%/, - "Narrow windows must preserve the same comfortable full-width reading transition."); - assert.match(css, - /main\.main-surface:not\(:has\(\[role="main"\]\)\)\s+article\s*\{[\s\S]*?background:\s*var\(--message-surface\)[\s\S]*?backdrop-filter:\s*blur\(14px\)\s+saturate\(108%\)/, - "Task messages must remain legible glass surfaces instead of hiding the artwork."); - assert.match(css, - /body\s*\{[\s\S]*?background-image:\s*var\(--dream-skin-art\)\s*!important[\s\S]*?main\.main-surface:not\(:has\(\[role="main"\]\)\)\s*\{[\s\S]*?overflow:\s*visible\s*!important[\s\S]*?background:\s*var\(--reading-veil\)\s*!important[\s\S]*?\.thread-scroll-container\s*\{[\s\S]*?background:\s*transparent\s*!important/, - "Wide ambient tasks must use one body artwork layer plus one main-canvas reading veil."); - const threadSurfaceBlocks = [...css.matchAll( - /([^{}]*\.thread-scroll-container[^{}]*)\{([^{}]*)\}/g, - )]; - assert.ok(threadSurfaceBlocks.length > 0, - "The renderer CSS fixture must include the native thread surface."); - for (const [, selector, declarations] of threadSurfaceBlocks) { - assert.doesNotMatch( - declarations, - /(?:^|\n)\s*(?:overflow(?:-[xy])?|clip-path|(?:-webkit-)?mask(?:-image)?)\s*:/, - `The skin must not override native scrolling or clipping on ${selector.trim()}.`, - ); - } - assert.doesNotMatch(css, /calc\(100%\s*-\s*(?:420|240)px\)/, - "Single-canvas atmosphere must not use viewport-specific right-column breakpoints."); - assert.match(css, - /main\.main-surface:not\(:has\(\[role="main"\]\)\)::after\s*\{[\s\S]*?content:\s*none\s*!important[\s\S]*?display:\s*none\s*!important/, - "Wide ambient task routes must not create an independently bounded artwork window."); - assert.doesNotMatch(css, /--ds-task-art-window|mask-image:(?!\s*none)/, - "The task atmosphere must not rely on a second mask with its own edge."); - assert.match(css, - /header\.app-header-tint\s*\{[\s\S]*?background:\s*var\(--composer-surface\)\s*!important[\s\S]*?border:\s*0\s*!important[\s\S]*?box-shadow:\s*none\s*!important/, - "Theme chrome must not draw a full-width divider below the native toolbar."); - assert.match(css, - /composer-surface-chrome::before\s*\{[\s\S]*?content:\s*""\s*!important[\s\S]*?border:\s*1px solid var\(--ds-border\)[\s\S]*?pointer-events:\s*none\s*!important[\s\S]*?composer-surface-chrome:focus-within\s*\{[\s\S]*?outline:\s*2px solid var\(--ds-focus\)[\s\S]*?outline-offset:\s*2px/, - "The composer must expose a stable one-pixel boundary layer and a two-pixel focus ring."); - assert.match(css, - /composer-surface-chrome\s*\{[\s\S]{0,260}?isolation:\s*isolate\s*!important[\s\S]{0,120}?z-index:\s*4\s*!important[\s\S]{0,260}?overflow:\s*visible\s*!important[\s\S]{0,260}?clip-path:\s*none\s*!important[\s\S]{0,260}?mask-image:\s*none\s*!important[\s\S]{0,260}?background-clip:\s*padding-box\s*!important[\s\S]{0,260}?background:\s*var\(--composer-surface\)\s*!important[\s\S]{0,180}?backdrop-filter:\s*blur\(20px\)\s+saturate\(108%\)\s*!important/, - "The composer must remain an independent, complete surface above atmospheric layers."); - assert.doesNotMatch(css, - /composer-surface-chrome\s*\{[\s\S]{0,240}?border:\s*0\s*!important/, - "No composer variant may remove the input boundary."); - assert.match(css, - /data-dream-shell="light"\][\s\S]*?composer-surface-chrome p\.placeholder::after\s*\{[\s\S]*?color:\s*var\(--ds-text-secondary\)\s*!important[\s\S]*?opacity:\s*1\s*!important/, - "Light-shell composer placeholders must keep opaque secondary-text contrast."); - assert.match(css, - /:is\(\[role="menu"\],\s*\[role="dialog"\]\)[\s\S]*?background:\s*var\(--ds-popover\)[\s\S]*?border:\s*1px solid var\(--ds-border\)/, - "Menus and dialogs must use an independent elevated popover surface."); // Every home/project selector must stay behind the root skin gate. A // marker-class-to-:has() conversion must never leave native layout rules // active after pause/restore. @@ -416,9 +281,8 @@ export async function runRendererRuntimeTest(assetRoot) { assert.equal(home.document.adoptedStyleSheets.length, 1); assert.equal(state.scope.baseState, "home"); assert.equal(state.scope.level, "L1"); - assert.equal(home.rootStyle.values.get("--dream-skin-brand-subtitle"), undefined); - assert.equal(home.rootStyle.values.get("--dream-skin-status"), undefined); - assert.equal(home.rootStyle.values.get("--dream-skin-quote"), undefined); + assert.equal(home.rootStyle.values.get("--dream-skin-brand-subtitle"), '"CODEX DREAM SKIN"'); + assert.equal(home.rootStyle.values.get("--dream-skin-status"), '"DREAM SKIN ONLINE"'); assert.equal(home.rootStyle.values.get("--ds-theme-surface-radius"), "12px"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-opacity"), "1"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-blur"), "0px"); diff --git a/tools/selectors.json b/tools/selectors.json index ea0f0994..06e22231 100644 --- a/tools/selectors.json +++ b/tools/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/windows/VERSION b/windows/VERSION index eac1e0ad..9075be49 100644 --- a/windows/VERSION +++ b/windows/VERSION @@ -1 +1 @@ -1.5.6 +1.5.5 diff --git a/windows/assets/dream-skin.css b/windows/assets/dream-skin.css index a3a580ed..bd112ec0 100644 --- a/windows/assets/dream-skin.css +++ b/windows/assets/dream-skin.css @@ -59,70 +59,6 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); - /* One-canvas layout contract. Native window chrome and content geometry - remain authoritative; artwork must adapt to the product, never reserve - layout space at the expense of typing or reading comfort. */ - --titlebar-safe-left: max(env(safe-area-inset-left), 0px); - --titlebar-safe-right: max(env(safe-area-inset-right), 0px); - --ambient-start: 52%; - --ambient-feather: clamp(240px, 24vw, 320px); - /* Semantic design tokens. Theme packages provide the primitive palette; - native components consume these roles so material, state and accessibility - decisions stay consistent instead of scattering alpha values. */ - --ds-canvas: var(--ds-bg); - --ds-sidebar: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane: rgb(var(--ds-panel-rgb) / .975); - --ds-content-reading-plane-soft: rgb(var(--ds-panel-rgb) / .92); - --ds-content-reading-plane-far: rgb(var(--ds-panel-rgb) / .83); - --reading-veil: linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - rgb(var(--ds-panel-rgb) / .06) calc(var(--ambient-start) + var(--ambient-feather)), - rgb(var(--ds-panel-rgb) / .06) 100%); - --ds-surface: var(--ds-panel); - --message-surface: rgb(var(--ds-panel-rgb) / .94); - --ds-elevated-surface: var(--ds-panel); - --ds-popover: rgb(var(--ds-panel-rgb) / .988); - --ds-input: rgb(var(--ds-panel-rgb) / .985); - --composer-surface: rgb(var(--ds-panel-rgb) / .92); - --ds-input-disabled: rgb(var(--ds-panel-2-rgb) / .96); - --ds-selected: rgb(var(--ds-panel-2-rgb) / .98); - --ds-hover: rgb(var(--ds-panel-2-rgb) / .72); - --ds-pressed: rgb(var(--ds-panel-2-rgb) / .92); - --ds-focus: var(--ds-accent); - --ds-border: var(--ds-line); - --ds-text-primary: var(--ds-text); - --ds-text-secondary: var(--ds-muted); - --ds-text-muted: rgb(var(--ds-muted-rgb) / .88); - --ds-danger: #ff9a8f; - --ds-danger-surface: #4b1f22; - --ds-danger-foreground: #fff0ed; - --ds-danger-border: #d87970; - --ds-success: #80c79b; - --ds-success-surface: #203d2d; - --ds-success-foreground: #e6f4e9; - --ds-success-border: #72a782; - --ds-warning-surface: #4c351c; - --ds-warning-foreground: #fff0cf; - --ds-warning-border: #cf984b; - --ds-info-surface: #223842; - --ds-info-foreground: #edf7fa; - --ds-info-border: #7899a6; - --ds-zone-surface: color-mix(in srgb, var(--ds-panel) 88%, var(--ds-panel-2) 12%); - --ds-zone-surface-raised: color-mix(in srgb, var(--ds-panel) 82%, var(--ds-panel-2) 18%); - --ds-zone-border: rgb(var(--ds-muted-rgb) / .32); - --ds-zone-divider: rgb(var(--ds-muted-rgb) / .18); - --ds-zone-shadow: - 0 7px 18px rgb(var(--ds-bg-rgb) / .09), - inset 0 1px rgb(var(--ds-panel-rgb) / .82); - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .12); - --ds-shadow-elevated: - 0 16px 38px rgb(var(--ds-bg-rgb) / .20), - 0 2px 8px rgb(var(--ds-bg-rgb) / .12); - --ds-motion-fast: 120ms; - --ds-motion-standard: 160ms; - --ds-motion-reveal: 210ms; - --ds-ease-out: cubic-bezier(.22, 1, .36, 1); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -171,24 +107,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { --ds-task-immersive-edge: rgb(var(--ds-panel-rgb) / .86); --ds-task-immersive-mid: rgb(var(--ds-panel-rgb) / .78); --ds-task-immersive-far: rgb(var(--ds-panel-rgb) / .66); - --ds-danger: #9c3d3a; - --ds-danger-surface: #f8e6e3; - --ds-danger-foreground: #6f2523; - --ds-danger-border: #b85650; - --ds-success: #45624d; - --ds-success-surface: #e4efe5; - --ds-success-foreground: #28543a; - --ds-success-border: #5f856b; - --ds-warning-surface: #f8edd7; - --ds-warning-foreground: #674515; - --ds-warning-border: #aa7a32; - --ds-info-surface: #e8eef1; - --ds-info-foreground: #284754; - --ds-info-border: #6f8b97; - --ds-shadow-surface: 0 8px 22px rgb(var(--ds-bg-rgb) / .10); - --ds-shadow-elevated: - 0 18px 42px rgb(var(--ds-bg-rgb) / .16), - 0 2px 8px rgb(var(--ds-bg-rgb) / .08); } /* Native token surfaces (dropdown/popover) resolve from Codex's own @@ -197,131 +115,6 @@ html[data-dream-skin="active"][data-dream-shell="light"] { skin variables so those surfaces inherit the theme in both shells. */ html[data-dream-skin="active"] { --color-token-dropdown-background: var(--ds-panel); - --vscode-inputValidation-errorBackground: var(--ds-danger-surface); - --vscode-inputValidation-errorForeground: var(--ds-danger-foreground); - --vscode-inputValidation-errorBorder: var(--ds-danger-border); - --color-token-input-validation-error-background: var(--ds-danger-surface); - --color-token-input-validation-error-foreground: var(--ds-danger-foreground); - --color-token-input-validation-error-border: var(--ds-danger-border); - --color-token-error-foreground: var(--ds-danger-foreground); - --color-token-editor-error-foreground: var(--ds-danger-foreground); -} - -/* Codex can remain electron-dark while a theme explicitly requests a light - shell. Bridge the native token family to the active skin so task cards, - inputs, secondary copy and icons do not keep dark-mode values on a light - wallpaper. This is component-scoped token remapping, never whole-page - opacity. */ -html[data-dream-skin="active"][data-dream-shell="light"] { - --vscode-foreground: var(--ds-text); - --vscode-descriptionForeground: var(--ds-muted); - --vscode-disabledForeground: rgb(var(--ds-muted-rgb) / .78); - --color-background-panel: var(--ds-panel); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: rgb(var(--ds-muted-rgb) / .96); - --color-token-text-tertiary: rgb(var(--ds-muted-rgb) / .88); - --color-token-conversation-body: var(--ds-muted); - --color-token-conversation-summary-leading: var(--ds-muted); - --color-token-conversation-summary-trailing: var(--ds-muted); - --color-token-muted-foreground: rgb(var(--ds-muted-rgb) / .92); - --color-token-description-foreground: var(--ds-muted); - --color-token-list-hover-background: var(--ds-hover); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* The scheduled-task detail side panel uses a separate inline panel surface - variable and input token family. Reinforce the light-shell bridge at its - native form boundary so the prompt, detail and schedule cards cannot retain - electron-dark surfaces or pale input copy. */ -html[data-dream-skin="active"][data-dream-shell="light"] - [class~="bg-token-main-surface-primary"]:has(form[id^="automation-side-panel-form"]) { - color: var(--ds-text); - --color-background-panel: var(--ds-input); - --color-token-bg-fog: var(--ds-panel-2); - --color-token-bg-secondary: var(--ds-panel-2); - --color-token-main-surface-primary: var(--ds-panel); - --color-token-main-surface-secondary: var(--ds-panel-2); - --color-token-foreground: var(--ds-text); - --color-token-text-primary: var(--ds-text); - --color-token-text-secondary: var(--ds-muted); - --color-token-description-foreground: var(--ds-muted); - --color-token-input-background: var(--ds-input); - --color-token-input-foreground: var(--ds-text); - --color-token-input-placeholder-foreground: var(--ds-muted); - --color-token-border: var(--ds-border); - --color-token-border-default: var(--ds-border); -} - -/* Native toasts use one generic foreground token across four different - surfaces. Resolve the actual host state classes into independent semantic - palettes before styling the shared alert component. */ -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-dropdown-background"] { - --ds-alert-surface: var(--ds-info-surface); - --ds-alert-foreground: var(--ds-info-foreground); - --ds-alert-border: var(--ds-info-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-info-background"] { - --ds-alert-surface: var(--ds-success-surface); - --ds-alert-foreground: var(--ds-success-foreground); - --ds-alert-border: var(--ds-success-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-warning-background"] { - --ds-alert-surface: var(--ds-warning-surface); - --ds-alert-foreground: var(--ds-warning-foreground); - --ds-alert-border: var(--ds-warning-border); -} - -html[data-dream-skin="active"] - [role="alert"].alert-root[class~="bg-token-input-validation-error-background"] { - --ds-alert-surface: var(--ds-danger-surface); - --ds-alert-foreground: var(--ds-danger-foreground); - --ds-alert-border: var(--ds-danger-border); -} - -html[data-dream-skin="active"] [role="alert"].alert-root { - background-color: var(--ds-alert-surface, var(--ds-popover)) !important; - border-color: var(--ds-alert-border, var(--ds-border)) !important; - color: var(--ds-alert-foreground, var(--ds-text)) !important; - --color-token-foreground: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-primary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-text-secondary: var(--ds-alert-foreground, var(--ds-text)); - --color-token-muted-foreground: var(--ds-alert-foreground, var(--ds-text)); -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label] { - color: var(--ds-alert-foreground, var(--ds-text)) !important; - opacity: .76 !important; -} - -html[data-dream-skin="active"] [role="alert"].alert-root > button[aria-label]:is(:hover, :focus-visible) { - opacity: 1 !important; -} - -/* Active tool/agent summaries render duplicated shimmer text with hard-coded - alpha and a white sweep. Keep the base label readable and tint only its - moving overlay with the skin accent; no ancestor opacity is changed. */ -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text { - color: var(--ds-muted) !important; - -webkit-text-fill-color: var(--ds-muted) !important; -} - -html[data-dream-skin="active"][data-dream-shell="light"] .loading-shimmer-pure-text * { - color: var(--ds-accent) !important; - -webkit-text-fill-color: var(--ds-accent) !important; } html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { @@ -337,10 +130,12 @@ html[data-dream-skin="active"] [class~="bg-token-dropdown-background"] { html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) { --ds-safe-side: left; + --ds-art-position: 100% var(--ds-focus-y); } html[data-dream-skin="active"]:is([data-dream-art-safe="right"], [data-dream-art-safe-area="right"]) { --ds-safe-side: right; + --ds-art-position: 0% var(--ds-focus-y); --ds-hero-scrim: linear-gradient(270deg, rgb(var(--ds-bg-rgb) / .90) 0%, rgb(var(--ds-bg-rgb) / .76) 50%, @@ -423,9 +218,9 @@ html[data-dream-skin="active"] body::before { html[data-dream-skin="active"] aside.app-shell-left-panel { background: linear-gradient(180deg, - var(--ds-sidebar), - rgb(var(--ds-bg-rgb) / .975)) !important; - border: 1px solid var(--ds-border) !important; + rgb(var(--ds-panel-rgb) / .98), + rgb(var(--ds-bg-rgb) / .96)) !important; + border: 1px solid var(--ds-line) !important; border-left: 0 !important; border-radius: 0 16px 16px 0 !important; box-shadow: 10px 0 30px rgb(var(--ds-bg-rgb) / .22) !important; @@ -435,53 +230,12 @@ html[data-dream-skin="active"] aside.app-shell-left-panel { html[data-dream-skin="active"] aside.app-shell-left-panel nav { background: transparent !important; } -/* Recent and other semantic lists may read as quiet raised sections. The - primary navigation stays on the native flat canvas: wrapping its split - fixed/scrolling DOM in a faux card creates the broken right edge called out - in the screenshot and relies on brittle nth-child geometry. */ -html[data-dream-skin="active"] aside.app-shell-left-panel - nav section:has([role="list"]) { - box-sizing: border-box !important; - margin-inline: 8px !important; - padding: 7px 6px 8px !important; - border: 1px solid var(--ds-zone-border) !important; - border-radius: 14px !important; - background: var(--ds-zone-surface-raised) !important; - box-shadow: var(--ds-zone-shadow) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel - nav section:has([role="list"]) > div > div:first-child { - margin-bottom: 3px !important; - padding-bottom: 5px !important; - border-bottom: 1px solid var(--ds-zone-divider) !important; -} - -/* Use the existing 16px native resize target as the visual boundary. The line - is non-interactive and never narrows or covers the resizer hit area. */ -html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]::before { - content: "" !important; - position: absolute !important; - top: 0 !important; - bottom: 0 !important; - left: 8px !important; - width: 1px !important; - background: var(--ds-zone-border) !important; - box-shadow: 4px 0 14px rgb(var(--ds-bg-rgb) / .08) !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel > [role="separator"]:hover::before { - background: rgb(var(--ds-accent-rgb) / .52) !important; -} - html[data-dream-skin="active"] aside.app-shell-left-panel button, html[data-dream-skin="active"] aside.app-shell-left-panel a { color: var(--ds-text) !important; - transition: background-color var(--ds-motion-standard) var(--ds-ease-out), - border-color var(--ds-motion-standard) var(--ds-ease-out), - color var(--ds-motion-fast) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out) !important; + transition: background-color .18s cubic-bezier(.22, 1, .36, 1), + border-color .18s cubic-bezier(.22, 1, .36, 1), + color .18s cubic-bezier(.22, 1, .36, 1) !important; } html[data-dream-skin="active"] aside.app-shell-left-panel [class*="text-token-foreground"] { @@ -497,9 +251,8 @@ html[data-dream-skin="active"] aside.app-shell-left-panel svg { } html[data-dream-skin="active"] aside.app-shell-left-panel button:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel a:hover, -html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:hover { - background: var(--ds-hover) !important; +html[data-dream-skin="active"] aside.app-shell-left-panel a:hover { + background: rgb(var(--ds-accent-rgb) / .09) !important; color: var(--ds-text) !important; transform: none !important; } @@ -526,23 +279,9 @@ html[data-dream-skin="active"] aside.app-shell-left-panel button[aria-label^=" html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"], html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-accent) !important; - border-radius: 9px 9px 10px 10px !important; - box-shadow: - 0 4px 14px rgb(var(--ds-bg-rgb) / .14), - inset 3px 0 rgb(var(--ds-accent-rgb) / .82) !important; - color: var(--ds-text) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel [role="button"]:active, -html[data-dream-skin="active"] aside.app-shell-left-panel button:active, -html[data-dream-skin="active"] aside.app-shell-left-panel a:active { - background: var(--ds-pressed) !important; -} - -html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] [class*="text-token"], -html[data-dream-skin="active"] aside.app-shell-left-panel [class~="bg-token-list-hover-background"] [class*="text-token"] { + background: rgb(var(--ds-accent-rgb) / .12) !important; + border: 1px solid rgb(var(--ds-accent-rgb) / .22) !important; + box-shadow: 0 4px 16px rgb(var(--ds-bg-rgb) / .12) !important; color: var(--ds-text) !important; } @@ -550,18 +289,6 @@ html[data-dream-skin="active"] aside.app-shell-left-panel [aria-current="page"] color: var(--ds-accent) !important; } -/* Onboarding and task suggestion cards are native sidebar surfaces, not - dialogs. When Codex itself is dark they otherwise remain charcoal beneath a - light skin. Keep them opaque enough for copy while preserving a soft lift. */ -html[data-dream-skin="active"][data-dream-shell="light"] aside.app-shell-left-panel - [class~="bg-token-main-surface-primary/70"] { - background: var(--ds-selected) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-surface) !important; - color: var(--ds-text) !important; - backdrop-filter: blur(14px) saturate(104%) !important; -} - html[data-dream-skin="active"] main.main-surface { position: relative !important; isolation: isolate; @@ -684,11 +411,10 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background: var(--reading-veil) !important; + background: linear-gradient(90deg, + var(--ds-task-immersive-edge), + var(--ds-task-immersive-mid) 64%, + var(--ds-task-immersive-far)) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -699,29 +425,21 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- content: none; } -/* The artwork is painted once on the fixed body layer. Do not create a second - right-side image window: its own width, mask and vertical bounds form a - visible page seam and can bisect the sticky composer region. */ -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"]))::after { - content: none !important; - display: none !important; -} - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(14px) saturate(102%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface > header.app-header-tint { - background: var(--composer-surface) !important; - border: 0 !important; + background: transparent !important; + border-bottom: 0 !important; box-shadow: none !important; backdrop-filter: none !important; } @@ -736,7 +454,9 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"][data-dream-shell="light"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] main.main-surface:not(:has([role="main"])) [class*="_markdown"] { - text-shadow: none; + text-shadow: + 0 1px 2px rgb(var(--ds-panel-rgb) / .92), + 0 0 10px rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"]:is([data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"]) @@ -773,26 +493,6 @@ html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) [role= 0 0 10px rgb(var(--ds-bg-rgb) / .46); } -/* Non-immersive task routes keep a conservative reading surface. Wide ambient - routes clear this local layer below because their veil already lives on the - complete main canvas, including the composer zone. */ -html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) - .thread-scroll-container { - background: - linear-gradient(90deg, - var(--ds-content-reading-plane) 0%, - var(--ds-content-reading-plane) var(--ambient-start), - var(--ds-content-reading-plane-far) 100%) !important; - backdrop-filter: none !important; -} - -html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"])) - .thread-scroll-container { - background: transparent !important; -} - html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) [role="main"] { text-shadow: none; } @@ -810,19 +510,19 @@ html[data-dream-skin="active"] main.main-surface html[data-dream-skin="active"] main.main-surface div[class~="bg-token-main-surface-primary"][class~="border-l"] { - background: var(--ds-content-reading-plane) !important; + background: rgb(var(--ds-panel-rgb) / .62) !important; backdrop-filter: blur(10px) saturate(106%) !important; } html[data-dream-skin="active"] main.main-surface:not(:has([role="main"])) article { border: 1px solid rgb(var(--ds-muted-rgb) / .12); - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .44); box-shadow: 0 8px 24px rgb(var(--ds-bg-rgb) / .10); - backdrop-filter: blur(14px) saturate(108%); + backdrop-filter: blur(7px) saturate(105%); } html[data-dream-skin="active"][data-dream-shell="light"] main.main-surface:not(:has([role="main"])) article { - background: var(--message-surface); + background: rgb(var(--ds-panel-rgb) / .72); } html[data-dream-skin="active"][data-dream-shell="light"] @@ -832,25 +532,70 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve the native flex layout, baselines and hit targets. A solid surface - is enough; a theme border/shadow becomes a full-width false divider. */ - background: var(--composer-surface) !important; - border: 0 !important; - box-shadow: none !important; - backdrop-filter: none !important; + /* Preserve Codex's native fixed header geometry and side-panel controls. */ + background: rgb(var(--ds-panel-rgb) / .90) !important; + border-bottom: 1px solid var(--ds-line) !important; + backdrop-filter: blur(14px) saturate(108%) !important; } -/* Native titlebar controls own the complete toolbar grid and hit targets. - Theme identity comes from the artwork and palette, so task chrome carries no - absolute-positioned labels and the home composer has no floating quote. */ +/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { + content: var(--dream-skin-name, "Codex Dream Skin") " · " + var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); + position: absolute; + left: 22px; + top: 4px; + z-index: 2; + max-width: min(42%, 360px); + overflow: hidden; + color: var(--ds-accent); + font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .03em; + text-overflow: ellipsis; + text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { + content: var(--dream-skin-status, "DREAM SKIN ONLINE"); + position: absolute; + right: 84px; + top: 13px; + z-index: 2; + max-width: 28%; + overflow: hidden; + color: var(--ds-muted); + font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .08em; + text-overflow: ellipsis; + text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); + white-space: nowrap; + pointer-events: none; +} + html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: none !important; - display: none !important; + content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); + position: absolute; + right: 28px; + bottom: 72px; + z-index: 2; + pointer-events: none; + color: rgb(var(--ds-accent-rgb) / .74); + font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; + letter-spacing: 0; + text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); + transform: rotate(-3deg); +} + +html[data-dream-skin="active"][data-dream-shell="light"] + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, +html[data-dream-skin="active"][data-dream-shell="light"] + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { + text-shadow: none; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -1144,81 +889,15 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { - position: relative !important; - isolation: isolate !important; - z-index: 4 !important; overflow: visible !important; - clip-path: none !important; - -webkit-mask-image: none !important; - mask-image: none !important; - background-clip: padding-box !important; - border: 1px solid var(--ds-border) !important; - border-width: 1px !important; - border-style: solid !important; - border-color: var(--ds-border) !important; + border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; border-radius: 22px !important; - background: var(--composer-surface) !important; - box-shadow: var(--ds-shadow-surface) !important; - backdrop-filter: blur(20px) saturate(108%) !important; - transition: - border-color var(--ds-motion-standard) var(--ds-ease-out), - box-shadow var(--ds-motion-standard) var(--ds-ease-out), - background-color var(--ds-motion-standard) var(--ds-ease-out) !important; -} - -/* Current Codex builds force the native composer border width to zero from a - higher-priority cascade layer. Draw the semantic 1px boundary separately so - it remains stable without changing content opacity or input geometry. */ -html[data-dream-skin="active"] .composer-surface-chrome::before { - content: "" !important; - position: absolute !important; - inset: 0 !important; - z-index: 2 !important; - border: 1px solid var(--ds-border) !important; - border-radius: inherit !important; - pointer-events: none !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:hover { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:hover::before { - border-color: var(--ds-accent) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:focus-within { - border-color: var(--ds-focus) !important; - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; - box-shadow: var(--ds-shadow-elevated) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:focus-within::before { - border-color: var(--ds-focus) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled), -html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"] { - color: var(--ds-text-muted) !important; - background: var(--ds-input-disabled) !important; - border-color: var(--ds-border) !important; - box-shadow: none !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has(:disabled)::before, -html[data-dream-skin="active"] .composer-surface-chrome[aria-disabled="true"]::before { - border-color: var(--ds-border) !important; -} - -html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"]) { - border-color: var(--ds-danger) !important; - outline-color: var(--ds-danger) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; + box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; + backdrop-filter: blur(16px) saturate(108%) !important; } -html[data-dream-skin="active"] .composer-surface-chrome:has([aria-invalid="true"])::before { - border-color: var(--ds-danger) !important; -} +html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -1301,12 +980,13 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--composer-surface) !important; - border: 1px solid var(--ds-border) !important; + background: var(--ds-immersive-composer-solid) !important; + border: 0 !important; box-shadow: - var(--ds-shadow-surface), + 0 10px 30px rgb(var(--ds-bg-rgb) / .20), + inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] @@ -1315,12 +995,12 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr 0 10px 28px rgb(var(--ds-bg-rgb) / .14), inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"][data-dream-shell="light"] .composer-surface-chrome p.placeholder::after { - color: var(--ds-text-secondary) !important; + color: rgb(var(--ds-muted-rgb) / .78) !important; opacity: 1 !important; } @@ -1333,7 +1013,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { margin-inline: 0 !important; padding-inline: 18px !important; border-radius: 22px 22px 0 0 !important; - background: var(--composer-surface) !important; + background: rgb(var(--ds-panel-rgb) / .94) !important; box-shadow: inset 1px 0 rgb(var(--ds-muted-rgb) / .18), inset -1px 0 rgb(var(--ds-muted-rgb) / .18), @@ -1343,7 +1023,7 @@ html[data-dream-skin="active"] [class*="_homeUtilityBar_"] { html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has([class*="_homeUtilityBar_"]) .composer-surface-chrome { - border: 1px solid var(--ds-border) !important; + border: 0 !important; border-radius: 0 0 22px 22px !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24), @@ -1353,12 +1033,12 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]):has( } html[data-dream-skin="active"][data-dream-art-wide="true"] [class*="_homeUtilityBar_"] { - background: var(--composer-surface) !important; + background: var(--ds-immersive-composer-solid) !important; box-shadow: inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: blur(20px) saturate(108%) !important; + backdrop-filter: none !important; } html[data-dream-skin="active"][data-dream-art-wide="true"] @@ -1376,7 +1056,7 @@ html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="tr inset 1px 0 var(--ds-immersive-line), inset -1px 0 var(--ds-immersive-line), inset 0 1px rgb(var(--ds-panel-rgb) / .72) !important; - backdrop-filter: blur(18px) saturate(108%) !important; + backdrop-filter: blur(8px) saturate(102%) !important; } html[data-dream-skin="active"] [class*="_homeUtilityBar_"] button, @@ -1401,55 +1081,7 @@ html[data-dream-skin="active"] .composer-surface-chrome button:not([class~="bg-t } html[data-dream-skin="active"] .composer-surface-chrome p.placeholder::after { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -html[data-dream-skin="active"] :is(input, textarea)::placeholder { - color: var(--ds-text-secondary) !important; - opacity: 1 !important; -} - -/* Menus, popovers and dialogs are independent elevated surfaces. Background - content may inform their material tint but must never remain legible through - them. */ -html[data-dream-skin="active"] :is([role="menu"], [role="dialog"]), -html[data-dream-skin="active"] [data-radix-popper-content-wrapper] > * { - color: var(--ds-text-primary) !important; - background: var(--ds-popover) !important; - border: 1px solid var(--ds-border) !important; - box-shadow: var(--ds-shadow-elevated) !important; - backdrop-filter: blur(18px) saturate(102%) !important; -} - -html[data-dream-skin="active"] :is( - button, - a, - input, - textarea, - [role="button"], - [role="menuitem"], - [tabindex] -):focus-visible { - outline: 2px solid var(--ds-focus) !important; - outline-offset: 2px !important; -} - -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -):disabled, -html[data-dream-skin="active"] :is( - button, - input, - textarea, - [role="button"], - [role="menuitem"] -)[aria-disabled="true"] { - color: var(--ds-text-muted) !important; + color: rgb(var(--ds-muted-rgb) / .82) !important; opacity: 1 !important; } @@ -1541,26 +1173,12 @@ html[data-dream-skin="active"] button[class~="bg-token-foreground"] { box-shadow: 0 5px 14px rgb(var(--ds-accent-rgb) / .20) !important; } -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:hover { - background: var(--ds-highlight) !important; -} - -html[data-dream-skin="active"] button[class~="bg-token-foreground"]:active { - transform: translateY(1px); - box-shadow: 0 2px 8px rgb(var(--ds-accent-rgb) / .18) !important; -} - html[data-dream-skin="active"] article, html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px; } @media (max-width: 1120px) { - html[data-dream-skin="active"] { - --ambient-start: 52%; - } html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { content: none; } - html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"] - main.main-surface:not(:has([role="main"]))::after { content: none; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) { --thread-content-max-width: min(940px, calc(100cqw - 30px)) !important; } html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) > div:first-child > div:first-child > div:first-child { width: calc(100% - 28px) !important; } } diff --git a/windows/assets/renderer-inject.js b/windows/assets/renderer-inject.js index 9ef10592..be799888 100644 --- a/windows/assets/renderer-inject.js +++ b/windows/assets/renderer-inject.js @@ -311,9 +311,11 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - // Header and lower-chrome slogans were retired because they compete with - // native toolbar controls and the composer. Keep the old property names in - // THEME_VARIABLES so reinjection removes values left by earlier versions. + setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); + setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( + THEME.brandSubtitle || "CODEX DREAM SKIN", + )); + setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/windows/assets/selectors.json b/windows/assets/selectors.json index ea0f0994..06e22231 100644 --- a/windows/assets/selectors.json +++ b/windows/assets/selectors.json @@ -40,7 +40,7 @@ { "key": "home-suggestions", "selector": ".group\\/home-suggestions", "tier": "L2", "scope": "home", "required": false }, { "key": "project-selector", "selector": ".group\\/project-selector", "tier": "L2", "scope": "home config", "required": false, "notes": "依赖用户项目配置;win 快照未出现(该机可能未配置项目),不算平台差异" }, { "key": "markdown", "selector": "[class*=\"_markdown\"]", "tier": "L2", "scope": "thread", "required": false, "notes": "会话内容块,单页可达 100+ 个,样式规则注意性能" }, - { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在。原生应用负责 overflow、裁剪与滚动虚拟化,皮肤不得覆盖这些功能属性" }, + { "key": "thread-surface", "selector": ".thread-scroll-container", "tier": "L2", "scope": "thread", "required": false, "notes": "会话滚动表面;双端活动会话快照均存在" }, { "key": "message", "selector": "[data-message-author-role]", "tier": "L2", "scope": "thread", "required": false, "notes": "用户与助手消息的稳定语义边界;运行时对子节点变更做合并刷新" }, { "key": "appearance-radio", "selector": "input[name=\"appearance-theme\"]", "tier": "L2", "scope": "settings", "required": false, "notes": "外观三选一;theme-preview testid 同屏出现" }, { "key": "overlay-menu", "selector": "[role=\"menu\"]", "tier": "L2", "scope": "overlay", "required": false }, diff --git a/windows/scripts/injector.mjs b/windows/scripts/injector.mjs index e55f936c..ebedabf7 100644 --- a/windows/scripts/injector.mjs +++ b/windows/scripts/injector.mjs @@ -39,7 +39,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.6"; +const SKIN_VERSION = "1.5.5"; const MAX_ART_BYTES = 10 * 1024 * 1024; const MAX_SAFE_CSS_BYTES = 256 * 1024; const STRONG_THEME_AUDIT_MS = 30000; @@ -1129,14 +1129,6 @@ export async function verifySession( ?? chainCandidates.findLast((item) => item?.visible) ?? siblingCandidates.find((item) => item?.visible) ?? box(boxableChain[boxableChain.length - 1]); - const threadSurfaceNode = document.querySelector(${selectorLiteral("thread-surface")}); - const threadSurfaceStyle = threadSurfaceNode ? getComputedStyle(threadSurfaceNode) : null; - const threadSurface = threadSurfaceNode ? { - ...box(threadSurfaceNode), - overflowX: threadSurfaceStyle.overflowX, - overflowY: threadSurfaceStyle.overflowY, - pointerEvents: threadSurfaceStyle.pointerEvents, - } : null; const result = { installed: document.documentElement.getAttribute('data-dream-skin') === 'active', version: runtime?.version ?? null, @@ -1158,7 +1150,6 @@ export async function verifySession( composer: box(document.querySelector(${selectorLiteral("composer-chrome")})), shell: box(document.querySelector(${selectorLiteral("shell-main")})), sidebar: box(document.querySelector(${selectorLiteral("left-panel")})), - threadSurface, nativeWindow: ${JSON.stringify(nativeWindow)}, documentVisibility: document.visibilityState ?? null, documentHidden: document.hidden === true, @@ -1175,11 +1166,6 @@ export async function verifySession( const documentPass = result.documentVisibility === 'visible' && !result.documentHidden; const viewportPass = result.viewport.width >= ${MIN_RENDERER_VIEWPORT_WIDTH} && result.viewport.height >= ${MIN_RENDERER_VIEWPORT_HEIGHT}; - const threadScrollPass = !result.threadSurface || ( - result.threadSurface.visible && - ['auto', 'scroll', 'overlay'].includes(result.threadSurface.overflowY) && - result.threadSurface.pointerEvents !== 'none' - ); const windowPass = result.nativeWindow?.pass === true; const expectedThemeId = ${JSON.stringify(expectedThemeId)}; const expectedRevision = ${JSON.stringify(expectedRevision)}; @@ -1187,16 +1173,10 @@ export async function verifySession( (!expectedRevision || result.revision === expectedRevision); result.expectedThemeId = expectedThemeId; result.expectedRevision = expectedRevision; - result.readiness = { - windowPass, - documentPass, - viewportPass, - structurePass, - threadScrollPass, - }; + result.readiness = { windowPass, documentPass, viewportPass, structurePass }; result.pass = result.installed && result.version === result.expectedVersion && result.stylePresent && result.businessClassPollution === 0 && windowPass && - documentPass && viewportPass && structurePass && threadScrollPass && + documentPass && viewportPass && structurePass && payloadPass && (!result.homePresent || (Boolean(result.homeSurface?.visible && result.hero?.visible) && (!result.suggestionsPresent || (result.cards.length >= 2 && result.cards.length <= 4)))); diff --git a/windows/tests/injector-window-readiness.test.mjs b/windows/tests/injector-window-readiness.test.mjs index ba5e4074..4a691b11 100644 --- a/windows/tests/injector-window-readiness.test.mjs +++ b/windows/tests/injector-window-readiness.test.mjs @@ -13,7 +13,6 @@ const selectors = { shell: "main.main-surface", sidebar: "aside.app-shell-left-panel", composer: ".composer-surface-chrome", - threadSurface: ".thread-scroll-container", home: '[role="main"]:has([data-testid="home-icon"])', settings: 'input[name="appearance-theme"]', themePreview: '[data-testid="theme-preview"]', @@ -51,9 +50,6 @@ function makeDomFixture({ shell = makeElement(), sidebar = makeElement(), composer = makeElement(), - threadSurface = makeElement({ - style: { overflowX: "hidden", overflowY: "auto", pointerEvents: "auto" }, - }), home = null, settings = null, visibilityState = "visible", @@ -78,7 +74,6 @@ function makeDomFixture({ if (selector === selectors.shell) return shell; if (selector === selectors.sidebar) return sidebar; if (selector === selectors.composer) return composer; - if (selector === selectors.threadSurface) return threadSurface; if (selector === selectors.home) return home; if (selector === selectors.settings || selector === selectors.themePreview) return settings; return null; @@ -88,7 +83,7 @@ function makeDomFixture({ }; const window = { __CODEX_DREAM_SKIN_STATE__: { - version: "1.5.6", + version: "1.5.5", themeId: "fixture-theme", revision: "fixture-revision", styleMode: "style", @@ -153,7 +148,6 @@ test("normal L1 renderer requires and records the exact target window binding", documentPass: true, viewportPass: true, structurePass: true, - threadScrollPass: true, }); assert.deepEqual(session.calls, [ { method: "Browser.getWindowForTarget", params: { targetId: "page-main" } }, @@ -161,18 +155,6 @@ test("normal L1 renderer requires and records the exact target window binding", ]); }); -test("task verification fails when the skin disables the native thread scroller", async () => { - const broken = await verify({ - dom: makeDomFixture({ - threadSurface: makeElement({ - style: { overflowX: "visible", overflowY: "visible", pointerEvents: "auto" }, - }), - }), - }); - assert.equal(broken.result.pass, false); - assert.equal(broken.result.readiness.threadScrollPass, false); -}); - test("visible settings and home anchors are the only L0 structure exceptions", async () => { const settings = await verify({ dom: makeDomFixture({ From 16da95b6e5bcb3c8513ab4f16e0b948b03785420 Mon Sep 17 00:00:00 2001 From: StephenHsu Date: Sun, 26 Jul 2026 07:02:52 -0400 Subject: [PATCH 3/3] Apply minimal single-canvas theme fix --- macos/CHANGELOG.md | 8 ++ macos/VERSION | 2 +- macos/assets/dream-skin.css | 128 ++++++++---------- macos/assets/renderer-inject.js | 8 +- macos/package.json | 2 +- macos/scripts/common-macos.sh | 2 +- macos/scripts/injector.mjs | 2 +- macos/tests/run-tests.sh | 4 +- runtime/dream-skin.css | 128 ++++++++---------- runtime/renderer-inject.js | 8 +- tools/renderer-runtime.test.mjs | 38 +++++- windows/VERSION | 2 +- windows/assets/dream-skin.css | 128 ++++++++---------- windows/assets/renderer-inject.js | 8 +- windows/scripts/injector.mjs | 2 +- .../tests/injector-window-readiness.test.mjs | 2 +- 16 files changed, 221 insertions(+), 251 deletions(-) diff --git a/macos/CHANGELOG.md b/macos/CHANGELOG.md index 349b5101..64f36775 100644 --- a/macos/CHANGELOG.md +++ b/macos/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.5.6 — 2026-07-26 + +### 修复 + +- 移除覆盖原生标题栏与首页输入区域的绝对定位主题标语,让窗口、导航、分享与设置控件继续使用原生布局和点击区域。 +- 宽幅任务背景继续只绘制在底层画布,主阅读平面改用 240–320px 连续羽化遮罩,不再用硬编码 64% 分段形成左右拼接感。 +- 输入框恢复完整的一像素边界和独立 surface,并清除会截断其表面的容器裁切、mask 与宽幅模式 `border: 0` 覆盖。 + ## 1.5.5 — 2026-07-25 ### 修复 diff --git a/macos/VERSION b/macos/VERSION index 9075be49..eac1e0ad 100644 --- a/macos/VERSION +++ b/macos/VERSION @@ -1 +1 @@ -1.5.5 +1.5.6 diff --git a/macos/assets/dream-skin.css b/macos/assets/dream-skin.css index bd112ec0..96985bc9 100644 --- a/macos/assets/dream-skin.css +++ b/macos/assets/dream-skin.css @@ -59,6 +59,16 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* Wide task routes remain one continuous canvas: artwork stays on the body, + while this veil feathers the reading plane into the atmospheric edge. */ + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + --composer-surface: rgb(var(--ds-panel-rgb) / .94); + --reading-veil: linear-gradient(90deg, + rgb(var(--ds-panel-rgb) / .96) 0%, + rgb(var(--ds-panel-rgb) / .96) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .12) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -411,10 +421,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -427,13 +438,12 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) @@ -532,70 +542,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets without + drawing a theme divider across the titlebar. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity stays in the artwork and palette, never positioned labels. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +853,32 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-line) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } -html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } +/* Keep a complete one-pixel boundary even when the native cascade zeroes the + composer's own border width. This layer never participates in input. */ +html[data-dream-skin="active"] .composer-surface-chrome::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-line) !important; + border-radius: inherit !important; + pointer-events: none !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +961,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] diff --git a/macos/assets/renderer-inject.js b/macos/assets/renderer-inject.js index be799888..7bb91013 100644 --- a/macos/assets/renderer-inject.js +++ b/macos/assets/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Their old variable names stay + // in THEME_VARIABLES so reinjection removes values from earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/macos/package.json b/macos/package.json index f8ebed43..b992b814 100644 --- a/macos/package.json +++ b/macos/package.json @@ -1,6 +1,6 @@ { "name": "codex-dream-skin-studio", - "version": "1.5.5", + "version": "1.5.6", "private": true, "type": "module", "scripts": { diff --git a/macos/scripts/common-macos.sh b/macos/scripts/common-macos.sh index 32a58da2..921ae633 100755 --- a/macos/scripts/common-macos.sh +++ b/macos/scripts/common-macos.sh @@ -29,7 +29,7 @@ CODEX_APP_JOB_LABEL="com.openai.codex-dream-skin-studio.app" INJECTOR_JOB_LABEL="com.openai.codex-dream-skin-studio.injector" EXPECTED_CODEX_TEAM_ID="2DC432GLL2" EXPECTED_CODEX_REQUIREMENT="anchor apple generic and certificate leaf[subject.OU] = \"$EXPECTED_CODEX_TEAM_ID\"" -SKIN_VERSION="1.5.5" +SKIN_VERSION="1.5.6" DREAM_SKIN_VALIDATED_RUNTIME_PID="" DREAM_SKIN_VALIDATED_RUNTIME_BUNDLE="" DREAM_SKIN_VALIDATED_RUNTIME_EXE="" diff --git a/macos/scripts/injector.mjs b/macos/scripts/injector.mjs index 924fe814..cbbbf764 100644 --- a/macos/scripts/injector.mjs +++ b/macos/scripts/injector.mjs @@ -42,7 +42,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.5"; +const SKIN_VERSION = "1.5.6"; const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]); const CDP_ID_PATTERN = /^[A-Za-z0-9._-]{1,200}$/; const MAX_ART_BYTES = 10 * 1024 * 1024; diff --git a/macos/tests/run-tests.sh b/macos/tests/run-tests.sh index ca068e6d..bfee73ff 100755 --- a/macos/tests/run-tests.sh +++ b/macos/tests/run-tests.sh @@ -82,7 +82,7 @@ UPDATE_JSON="$({ })" "$NODE" -e ' const value = JSON.parse(process.argv[1]); - if (value.currentVersion !== "v1.5.5" || value.latestVersion !== "v9.8.7") process.exit(1); + if (value.currentVersion !== "v1.5.6" || value.latestVersion !== "v9.8.7") process.exit(1); if (!value.updateAvailable) process.exit(1); if (value.releaseUrl !== "https://github.com/Fei-Away/Codex-Dream-Skin/releases/latest") process.exit(1); ' "$UPDATE_JSON" @@ -1063,7 +1063,7 @@ CRLF_BACKUP="$TMP/config-crlf-backup.json" "$NODE" "$ROOT/scripts/theme-config.mjs" restore "$CRLF_CONFIG" "$CRLF_BACKUP" >/dev/null /usr/bin/cmp -s "$CRLF_CONFIG" "$TMP/original-crlf.toml" -/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.5" ]' _ "$ROOT" +/usr/bin/env -u HOME /bin/bash -c '. "$1/scripts/common-macos.sh"; [ -n "$HOME" ] && [ "$SKIN_VERSION" = "1.5.6" ]' _ "$ROOT" if [ "${CODEX_DREAM_SKIN_SKIP_DOCTOR:-0}" = "1" ]; then printf 'SKIP: Doctor requires an installed, signed Codex app.\n' DOCTOR_RESULT="skipped" diff --git a/runtime/dream-skin.css b/runtime/dream-skin.css index b2559ffc..4a243094 100644 --- a/runtime/dream-skin.css +++ b/runtime/dream-skin.css @@ -59,6 +59,16 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* Wide task routes remain one continuous canvas: artwork stays on the body, + while this veil feathers the reading plane into the atmospheric edge. */ + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + --composer-surface: rgb(var(--ds-panel-rgb) / .94); + --reading-veil: linear-gradient(90deg, + rgb(var(--ds-panel-rgb) / .96) 0%, + rgb(var(--ds-panel-rgb) / .96) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .12) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -411,10 +421,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -427,13 +438,12 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(__DREAM_SELECTOR_SHELL_MAIN__):not(:has(__DREAM_SELECTOR_SHELL_MAIN__ __DREAM_SELECTOR_HOME_ROUTE_CSS__)) @@ -532,70 +542,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] __DREAM_SELECTOR_HOME_R } html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__ > __DREAM_SELECTOR_HEADER_TINT__ { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets without + drawing a theme divider across the titlebar. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity stays in the artwork and palette, never positioned labels. */ html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, html[data-dream-skin="active"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after, html[data-dream-skin="active"] __DREAM_SELECTOR_SHELL_MAIN__:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::before, -html[data-dream-skin="active"][data-dream-shell="light"] - __DREAM_SELECTOR_SHELL_MAIN__:not(:has(__DREAM_SELECTOR_HOME_ROUTE_CSS__)) > __DREAM_SELECTOR_HEADER_TINT__::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +853,32 @@ html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HO html[data-dream-skin="active"] __DREAM_SELECTOR_HOME_ROUTE__ __DREAM_SELECTOR_HOME_SUGGESTIONS__ button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__ { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-line) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } -html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { content: none !important; } +/* Keep a complete one-pixel boundary even when the native cascade zeroes the + composer's own border width. This layer never participates in input. */ +html[data-dream-skin="active"] __DREAM_SELECTOR_COMPOSER_CHROME__::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-line) !important; + border-radius: inherit !important; + pointer-events: none !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +961,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_SHEL } html[data-dream-skin="active"][data-dream-art-wide="true"] __DREAM_SELECTOR_COMPOSER_CHROME__ { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] diff --git a/runtime/renderer-inject.js b/runtime/renderer-inject.js index 4066c711..0ec08e42 100644 --- a/runtime/renderer-inject.js +++ b/runtime/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Their old variable names stay + // in THEME_VARIABLES so reinjection removes values from earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/tools/renderer-runtime.test.mjs b/tools/renderer-runtime.test.mjs index c0492567..6e762ff9 100644 --- a/tools/renderer-runtime.test.mjs +++ b/tools/renderer-runtime.test.mjs @@ -258,9 +258,36 @@ export async function runRendererRuntimeTest(assetRoot) { assert.match(css, /main\.main-surface:has\(\[role="main"\]\)/); assert.match(css, /main\.main-surface:not\(:has\(\[role="main"\]\)\)/); assert.doesNotMatch(css, /:has\([^()]*:has\(/); - assert.match(css, /content:\s*var\(--dream-skin-name[\s\S]{0,180}var\(--dream-skin-brand-subtitle/); - assert.match(css, /content:\s*var\(--dream-skin-status/); - assert.match(css, /content:\s*var\(--dream-skin-quote/); + assert.doesNotMatch(css, + /content:\s*var\(--dream-skin-(?:chrome-mark|brand-subtitle|status|quote)/, + "Theme slogans must not be painted over native chrome."); + assert.doesNotMatch(template, + /setStyleProperty\(root,\s*"--dream-skin-(?:brand-subtitle|status|quote)"/, + "Retired chrome slogans must not be populated by the renderer."); + assert.match(css, + /--ambient-start:\s*52%[\s\S]*?--ambient-feather:\s*clamp\(240px,\s*24vw,\s*320px\)[\s\S]*?--composer-surface:[\s\S]*?--reading-veil:\s*linear-gradient/); + assert.match(css, + /data-dream-task-mode="ambient"[\s\S]*?main\.main-surface:not\(:has\(\[role="main"\]\)\)\s*\{[\s\S]*?overflow:\s*visible\s*!important[\s\S]*?clip-path:\s*none\s*!important[\s\S]*?mask-image:\s*none\s*!important[\s\S]*?background:\s*var\(--reading-veil\)\s*!important/, + "Wide ambient tasks must feather one body artwork layer through a continuous main canvas."); + assert.match(css, + /header\.app-header-tint\s*\{[\s\S]*?background:\s*var\(--composer-surface\)\s*!important[\s\S]*?border:\s*0\s*!important[\s\S]*?box-shadow:\s*none\s*!important/, + "The theme must not draw a divider across the native toolbar."); + assert.match(css, + /composer-surface-chrome\s*\{[\s\S]{0,260}?isolation:\s*isolate\s*!important[\s\S]{0,180}?overflow:\s*visible\s*!important[\s\S]{0,220}?clip-path:\s*none\s*!important[\s\S]{0,220}?mask-image:\s*none\s*!important[\s\S]{0,220}?background:\s*var\(--composer-surface\)\s*!important/, + "The composer must stay one complete surface above atmospheric layers."); + assert.match(css, + /composer-surface-chrome::before\s*\{[\s\S]*?border:\s*1px solid var\(--ds-line\)[\s\S]*?pointer-events:\s*none\s*!important/, + "The composer boundary must remain complete without intercepting input."); + const threadSurfaceBlocks = [...css.matchAll( + /([^{}]*\.thread-scroll-container[^{}]*)\{([^{}]*)\}/g, + )]; + for (const [, selector, declarations] of threadSurfaceBlocks) { + assert.doesNotMatch( + declarations, + /(?:^|\n)\s*(?:overflow(?:-[xy])?|clip-path|(?:-webkit-)?mask(?:-image)?)\s*:/, + `The skin must not override native scrolling or clipping on ${selector.trim()}.`, + ); + } assert.match(css, /--ds-task-full-veil/); assert.match(css, /data-dream-task-mode="full"/); assert.match(css, /background-image:\s*var\(--ds-task-full-veil\),\s*var\(--dream-skin-art\)/); @@ -281,8 +308,9 @@ export async function runRendererRuntimeTest(assetRoot) { assert.equal(home.document.adoptedStyleSheets.length, 1); assert.equal(state.scope.baseState, "home"); assert.equal(state.scope.level, "L1"); - assert.equal(home.rootStyle.values.get("--dream-skin-brand-subtitle"), '"CODEX DREAM SKIN"'); - assert.equal(home.rootStyle.values.get("--dream-skin-status"), '"DREAM SKIN ONLINE"'); + assert.equal(home.rootStyle.values.has("--dream-skin-brand-subtitle"), false); + assert.equal(home.rootStyle.values.has("--dream-skin-status"), false); + assert.equal(home.rootStyle.values.has("--dream-skin-quote"), false); assert.equal(home.rootStyle.values.get("--ds-theme-surface-radius"), "12px"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-opacity"), "1"); assert.equal(home.rootStyle.values.get("--ds-theme-surface-blur"), "0px"); diff --git a/windows/VERSION b/windows/VERSION index 9075be49..eac1e0ad 100644 --- a/windows/VERSION +++ b/windows/VERSION @@ -1 +1 @@ -1.5.5 +1.5.6 diff --git a/windows/assets/dream-skin.css b/windows/assets/dream-skin.css index bd112ec0..96985bc9 100644 --- a/windows/assets/dream-skin.css +++ b/windows/assets/dream-skin.css @@ -59,6 +59,16 @@ --ds-task-immersive-edge: rgb(var(--ds-bg-rgb) / .82); --ds-task-immersive-mid: rgb(var(--ds-bg-rgb) / .74); --ds-task-immersive-far: rgb(var(--ds-bg-rgb) / .60); + /* Wide task routes remain one continuous canvas: artwork stays on the body, + while this veil feathers the reading plane into the atmospheric edge. */ + --ambient-start: 52%; + --ambient-feather: clamp(240px, 24vw, 320px); + --composer-surface: rgb(var(--ds-panel-rgb) / .94); + --reading-veil: linear-gradient(90deg, + rgb(var(--ds-panel-rgb) / .96) 0%, + rgb(var(--ds-panel-rgb) / .96) var(--ambient-start), + rgb(var(--ds-panel-rgb) / .12) calc(var(--ambient-start) + var(--ambient-feather)), + rgb(var(--ds-panel-rgb) / .06) 100%); } html[data-dream-skin="active"][data-dream-shell="light"] { @@ -411,10 +421,11 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) main.main-surface:not(:has([role="main"])) { - background: linear-gradient(90deg, - var(--ds-task-immersive-edge), - var(--ds-task-immersive-mid) 64%, - var(--ds-task-immersive-far)) !important; + overflow: visible !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background: var(--reading-veil) !important; border: 0 !important; border-radius: 0 !important; box-shadow: none !important; @@ -427,13 +438,12 @@ html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream- html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"]:is([data-dream-task-mode="ambient"], [data-dream-art-task-mode="ambient"], [data-dream-task-mode="banner"], [data-dream-art-task-mode="banner"])[data-dream-art-wide="true"]:has(main.main-surface):not(:has(main.main-surface [role="main"])) @@ -532,70 +542,24 @@ html[data-dream-skin="active"][data-dream-shell="light"] [role="main"]:has([data } html[data-dream-skin="active"] main.main-surface > header.app-header-tint { - /* Preserve Codex's native fixed header geometry and side-panel controls. */ - background: rgb(var(--ds-panel-rgb) / .90) !important; - border-bottom: 1px solid var(--ds-line) !important; - backdrop-filter: blur(14px) saturate(108%) !important; + /* Preserve the native flex layout, baselines and hit targets without + drawing a theme divider across the titlebar. */ + background: var(--composer-surface) !important; + border: 0 !important; + box-shadow: none !important; + backdrop-filter: none !important; } -/* Decorative chrome lives on the native shell; no injected positioning host is needed. */ +/* Native titlebar controls own the complete toolbar grid and hit targets. + Theme identity stays in the artwork and palette, never positioned labels. */ html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before { - content: var(--dream-skin-name, "Codex Dream Skin") " · " - var(--dream-skin-brand-subtitle, "CODEX DREAM SKIN"); - position: absolute; - left: 22px; - top: 4px; - z-index: 2; - max-width: min(42%, 360px); - overflow: hidden; - color: var(--ds-accent); - font: 800 11px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .03em; - text-overflow: ellipsis; - text-shadow: 0 1px 12px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, html[data-dream-skin="active"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - content: var(--dream-skin-status, "DREAM SKIN ONLINE"); - position: absolute; - right: 84px; - top: 13px; - z-index: 2; - max-width: 28%; - overflow: hidden; - color: var(--ds-muted); - font: 700 9px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - letter-spacing: .08em; - text-overflow: ellipsis; - text-shadow: 0 1px 10px rgb(var(--ds-bg-rgb) / .32); - white-space: nowrap; - pointer-events: none; -} - + main.main-surface:not(:has([role="main"])) > header.app-header-tint::after, html[data-dream-skin="active"] main.main-surface:has([role="main"])::after { - content: var(--dream-skin-quote, "MAKE SOMETHING WONDERFUL"); - position: absolute; - right: 28px; - bottom: 72px; - z-index: 2; - pointer-events: none; - color: rgb(var(--ds-accent-rgb) / .74); - font: italic 14px/1.2 "Segoe Print", "Comic Sans MS", cursive; - letter-spacing: 0; - text-shadow: 0 0 13px rgb(var(--ds-accent-rgb) / .30); - transform: rotate(-3deg); -} - -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::before, -html[data-dream-skin="active"][data-dream-shell="light"] - main.main-surface:not(:has([role="main"])) > header.app-header-tint::after { - text-shadow: none; + content: none !important; + display: none !important; } /* Windows keeps this native bar outside main; macOS simply has no match. */ @@ -889,15 +853,32 @@ html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .gro html[data-dream-skin="active"] [role="main"]:has([data-testid="home-icon"]) .group\/home-suggestions button > span:last-child { align-items: center !important; text-align: center !important; } html[data-dream-skin="active"] .composer-surface-chrome { + position: relative !important; + isolation: isolate !important; + z-index: 4 !important; overflow: visible !important; - border: 1px solid rgb(var(--ds-muted-rgb) / .18) !important; + clip-path: none !important; + -webkit-mask-image: none !important; + mask-image: none !important; + background-clip: padding-box !important; + border: 1px solid var(--ds-line) !important; border-radius: 22px !important; - background: rgb(var(--ds-panel-rgb) / .94) !important; + background: var(--composer-surface) !important; box-shadow: 0 10px 28px rgb(var(--ds-bg-rgb) / .24) !important; - backdrop-filter: blur(16px) saturate(108%) !important; + backdrop-filter: blur(18px) saturate(108%) !important; } -html[data-dream-skin="active"] .composer-surface-chrome::before { content: none !important; } +/* Keep a complete one-pixel boundary even when the native cascade zeroes the + composer's own border width. This layer never participates in input. */ +html[data-dream-skin="active"] .composer-surface-chrome::before { + content: "" !important; + position: absolute !important; + inset: 0 !important; + z-index: 2 !important; + border: 1px solid var(--ds-line) !important; + border-radius: inherit !important; + pointer-events: none !important; +} /* A wide image can carry the home screen too. The native hero remains live, but the artwork is painted once on the window instead of repeated in a card. */ @@ -980,13 +961,12 @@ html[data-dream-skin="active"][data-dream-art-wide="true"] main.main-surface:has } html[data-dream-skin="active"][data-dream-art-wide="true"] .composer-surface-chrome { - background: var(--ds-immersive-composer-solid) !important; - border: 0 !important; + background: var(--composer-surface) !important; + border: 1px solid var(--ds-line) !important; box-shadow: 0 10px 30px rgb(var(--ds-bg-rgb) / .20), - inset 0 0 0 1px var(--ds-immersive-line), inset 0 1px rgb(var(--ds-text-rgb) / .12) !important; - backdrop-filter: none !important; + backdrop-filter: blur(18px) saturate(108%) !important; } html[data-dream-skin="active"][data-dream-shell="light"][data-dream-art-wide="true"] diff --git a/windows/assets/renderer-inject.js b/windows/assets/renderer-inject.js index be799888..7bb91013 100644 --- a/windows/assets/renderer-inject.js +++ b/windows/assets/renderer-inject.js @@ -311,11 +311,9 @@ } setStyleProperty(root, "--dream-skin-name", cssString(THEME.name || "Codex Dream Skin")); setStyleProperty(root, "--dream-skin-tagline", cssString(THEME.tagline || "Make something wonderful.")); - setStyleProperty(root, "--dream-skin-quote", cssString(THEME.quote || "MAKE SOMETHING WONDERFUL")); - setStyleProperty(root, "--dream-skin-brand-subtitle", cssString( - THEME.brandSubtitle || "CODEX DREAM SKIN", - )); - setStyleProperty(root, "--dream-skin-status", cssString(THEME.statusText || "DREAM SKIN ONLINE")); + // Header and lower-chrome slogans were retired because they compete with + // native toolbar controls and the composer. Their old variable names stay + // in THEME_VARIABLES so reinjection removes values from earlier versions. setStyleProperty(root, "--dream-skin-project-prefix", cssString(THEME.projectPrefix || "选择项目 · ")); setStyleProperty(root, "--dream-skin-project-label", cssString(THEME.projectLabel || "◉ 选择项目")); }; diff --git a/windows/scripts/injector.mjs b/windows/scripts/injector.mjs index ebedabf7..bf04480f 100644 --- a/windows/scripts/injector.mjs +++ b/windows/scripts/injector.mjs @@ -39,7 +39,7 @@ const stableTestidLiteral = (testid) => { } return JSON.stringify(`[data-testid="${testid}"]`); }; -const SKIN_VERSION = "1.5.5"; +const SKIN_VERSION = "1.5.6"; const MAX_ART_BYTES = 10 * 1024 * 1024; const MAX_SAFE_CSS_BYTES = 256 * 1024; const STRONG_THEME_AUDIT_MS = 30000; diff --git a/windows/tests/injector-window-readiness.test.mjs b/windows/tests/injector-window-readiness.test.mjs index 4a691b11..39cd8914 100644 --- a/windows/tests/injector-window-readiness.test.mjs +++ b/windows/tests/injector-window-readiness.test.mjs @@ -83,7 +83,7 @@ function makeDomFixture({ }; const window = { __CODEX_DREAM_SKIN_STATE__: { - version: "1.5.5", + version: "1.5.6", themeId: "fixture-theme", revision: "fixture-revision", styleMode: "style",