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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### 改进

- 路由可读性层不再等待 180ms 后才更新。统一 runtime 在 Navigation API 事件同一轮写入 `data-dream-route-phase`,下一动画帧按选择器契约复测 scope,并以 380ms 曲线驱动单一继承进度;侧栏与主区使用同一数值叠加任务页 veil,避免两列透明度不同步或梯度瞬间跳变。实现不监听 DOM subtree、不写业务 class、不读布局,并遵守 reduced-motion。

## 1.3.3 — 2026-07-23

### 修复
Expand Down
80 changes: 74 additions & 6 deletions macos/assets/dream-skin.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
@property --ds-route-progress {
syntax: "<number>";
inherits: true;
initial-value: 1;
}

/* Canonical cross-platform skin. Run tools/sync-runtime-assets.mjs after editing. */
:root[data-dream-skin="active"] {
color-scheme: dark !important;
Expand Down Expand Up @@ -56,6 +62,14 @@
--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);
--ds-task-sidebar-overlay: rgb(var(--ds-panel-rgb) / .44);
--ds-task-sidebar-edge-overlay: rgb(var(--ds-bg-rgb) / .70);
--ds-task-overlay-edge: rgb(var(--ds-bg-rgb) / .70);
--ds-task-overlay-mid: rgb(var(--ds-bg-rgb) / .65);
--ds-task-overlay-far: rgb(var(--ds-bg-rgb) / .52);
--ds-route-progress: 1;
--ds-route-transition: 380ms cubic-bezier(.25, .32, .25, 1);
transition: --ds-route-progress var(--ds-route-transition);
}

html[data-dream-skin="active"][data-dream-shell="light"] {
Expand Down Expand Up @@ -101,6 +115,23 @@ 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-task-sidebar-overlay: rgb(var(--ds-panel-rgb) / .46);
--ds-task-sidebar-edge-overlay: rgb(var(--ds-panel-rgb) / .75);
--ds-task-overlay-edge: rgb(var(--ds-panel-rgb) / .75);
--ds-task-overlay-mid: rgb(var(--ds-panel-rgb) / .69);
--ds-task-overlay-far: rgb(var(--ds-panel-rgb) / .60);
}

/* Navigation begins at the click/navigation event, not after route DOM settles.
The renderer advances to "settling" on the next animation frame so Chromium
can interpolate one inherited value without layout reads or subtree watches. */
html[data-dream-skin="active"][data-dream-route-phase="prepare"] {
--ds-route-progress: 0;
transition: none !important;
}

html[data-dream-skin="active"][data-dream-route-phase="settling"] {
--ds-route-progress: 1;
}

html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) {
Expand Down Expand Up @@ -316,12 +347,35 @@ 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"]))
aside.app-shell-left-panel {
background: linear-gradient(90deg,
var(--ds-task-immersive-sidebar),
var(--ds-task-immersive-edge) 100%) !important;
var(--ds-immersive-sidebar),
var(--ds-immersive-edge) 100%) !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
backdrop-filter: none !important;
isolation: isolate;
}

/* The sidebar and main surface share this inherited progress value. Keeping the
stronger task wash in separate overlays avoids a discrete gradient swap. */
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"]))
aside.app-shell-left-panel::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(90deg,
var(--ds-task-sidebar-overlay),
var(--ds-task-sidebar-edge-overlay));
opacity: var(--ds-route-progress);
will-change: opacity;
}

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"]))
aside.app-shell-left-panel > :first-child {
position: relative;
z-index: 1;
}

/* Native sidebar resize chrome inherits the sidebar background and extends
Expand All @@ -334,17 +388,27 @@ 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;
var(--ds-immersive-edge),
var(--ds-immersive-mid) 64%,
var(--ds-immersive-far)) !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: 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"]))::before {
content: none;
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(90deg,
var(--ds-task-overlay-edge),
var(--ds-task-overlay-mid) 64%,
var(--ds-task-overlay-far));
opacity: var(--ds-route-progress);
will-change: opacity;
}

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"]))
Expand Down Expand Up @@ -1084,6 +1148,10 @@ html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px;
}

@media (prefers-reduced-motion: reduce) {
:root[data-dream-skin="active"] {
--ds-route-transition: 0ms linear;
}

html[data-dream-skin="active"] *,
html[data-dream-skin="active"] *::before,
html[data-dream-skin="active"] *::after {
Expand Down
49 changes: 47 additions & 2 deletions macos/assets/renderer-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"data-dream-skin", SHELL_ATTR,
"data-dream-art-wide", "data-dream-art-safe", "data-dream-task-mode",
"data-dream-art-safe-area", "data-dream-art-task-mode", "data-dream-art-aspect",
"data-dream-art-ready",
"data-dream-art-ready", "data-dream-route-phase",
];
const ROUTE_TRANSITION_MS = 380;
const VERSION = __DREAM_SKIN_VERSION_JSON__;
const STYLE_REVISION = __DREAM_SKIN_STYLE_REVISION_JSON__;
const PAYLOAD_REVISION = __DREAM_SKIN_PAYLOAD_REVISION_JSON__;
Expand Down Expand Up @@ -58,6 +59,7 @@
styleWrites: 0,
styleRepairs: 0,
navigationEvents: 0,
routeTransitions: 0,
safetyPasses: 0,
analysisRuns: 0,
analysisCacheHits: artAnalysis ? 1 : 0,
Expand Down Expand Up @@ -586,6 +588,16 @@
}
if (state?.timer) clearInterval(state.timer);
if (state?.scheduler?.timeout) clearTimeout(state.scheduler.timeout);
if (state?.routeTransition?.frame !== null) {
if (typeof window.cancelAnimationFrame === "function") {
window.cancelAnimationFrame(state.routeTransition.frame);
} else {
clearTimeout(state.routeTransition.frame);
}
}
if (state?.routeTransition?.timeout !== null) {
clearTimeout(state.routeTransition.timeout);
}
if (analysisTimer) clearTimeout(analysisTimer);
if (state?.mediaHandler && state?.mediaQuery) {
try { state.mediaQuery.removeEventListener("change", state.mediaHandler); } catch {}
Expand Down Expand Up @@ -623,6 +635,37 @@
if (scheduler.timeout) return;
scheduler.timeout = setTimeout(flushScheduledEnsure, delay);
};
const routeTransition = { frame: null, timeout: null };
const requestRouteFrame = (callback) => typeof window.requestAnimationFrame === "function"
? window.requestAnimationFrame(callback)
: setTimeout(callback, 0);
const cancelRouteFrame = (frame) => {
if (frame === null) return;
if (typeof window.cancelAnimationFrame === "function") window.cancelAnimationFrame(frame);
else clearTimeout(frame);
};
const beginRouteTransition = () => {
const root = document.documentElement;
if (!root) return;
cancelRouteFrame(routeTransition.frame);
routeTransition.frame = null;
if (routeTransition.timeout !== null) clearTimeout(routeTransition.timeout);
routeTransition.timeout = null;
setAttribute(root, "data-dream-route-phase", "prepare");
metrics.routeTransitions += 1;
routeTransition.frame = requestRouteFrame(() => {
routeTransition.frame = null;
if (window[DISABLED_KEY] || window[STATE_KEY]?.installToken !== installToken) return;
setAttribute(root, "data-dream-route-phase", "settling");
ensure({ scope: true });
routeTransition.timeout = setTimeout(() => {
routeTransition.timeout = null;
if (window[STATE_KEY]?.installToken === installToken) {
root.removeAttribute("data-dream-route-phase");
}
}, ROUTE_TRANSITION_MS);
});
};
if (typeof MutationObserver === "function") {
rootObserver = new MutationObserver(() => scheduleEnsure({ root: true }));
}
Expand All @@ -638,7 +681,7 @@
? window.navigation : null;
const navigationHandler = navigationApi ? () => {
metrics.navigationEvents += 1;
scheduleEnsure({ scope: true }, 180);
beginRouteTransition();
} : null;

window[STATE_KEY] = {
Expand All @@ -647,6 +690,7 @@
rootObserver,
timer: null,
scheduler,
routeTransition,
mediaQuery,
mediaHandler,
navigation: navigationApi,
Expand All @@ -666,6 +710,7 @@
themeId: THEME.id || "custom",
revision: PAYLOAD_REVISION,
detectShellAppearance,
beginRouteTransition,
};
const firstEnsureStartedAt = now();
ensure({ root: true });
Expand Down
80 changes: 74 additions & 6 deletions runtime/dream-skin.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
@property --ds-route-progress {
syntax: "<number>";
inherits: true;
initial-value: 1;
}

/* Canonical cross-platform skin. Run tools/sync-runtime-assets.mjs after editing. */
:root[data-dream-skin="active"] {
color-scheme: dark !important;
Expand Down Expand Up @@ -56,6 +62,14 @@
--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);
--ds-task-sidebar-overlay: rgb(var(--ds-panel-rgb) / .44);
--ds-task-sidebar-edge-overlay: rgb(var(--ds-bg-rgb) / .70);
--ds-task-overlay-edge: rgb(var(--ds-bg-rgb) / .70);
--ds-task-overlay-mid: rgb(var(--ds-bg-rgb) / .65);
--ds-task-overlay-far: rgb(var(--ds-bg-rgb) / .52);
--ds-route-progress: 1;
--ds-route-transition: 380ms cubic-bezier(.25, .32, .25, 1);
transition: --ds-route-progress var(--ds-route-transition);
}

html[data-dream-skin="active"][data-dream-shell="light"] {
Expand Down Expand Up @@ -101,6 +115,23 @@ 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-task-sidebar-overlay: rgb(var(--ds-panel-rgb) / .46);
--ds-task-sidebar-edge-overlay: rgb(var(--ds-panel-rgb) / .75);
--ds-task-overlay-edge: rgb(var(--ds-panel-rgb) / .75);
--ds-task-overlay-mid: rgb(var(--ds-panel-rgb) / .69);
--ds-task-overlay-far: rgb(var(--ds-panel-rgb) / .60);
}

/* Navigation begins at the click/navigation event, not after route DOM settles.
The renderer advances to "settling" on the next animation frame so Chromium
can interpolate one inherited value without layout reads or subtree watches. */
html[data-dream-skin="active"][data-dream-route-phase="prepare"] {
--ds-route-progress: 0;
transition: none !important;
}

html[data-dream-skin="active"][data-dream-route-phase="settling"] {
--ds-route-progress: 1;
}

html[data-dream-skin="active"]:is([data-dream-art-safe="left"], [data-dream-art-safe-area="left"]) {
Expand Down Expand Up @@ -316,12 +347,35 @@ 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_LEFT_PANEL__ {
background: linear-gradient(90deg,
var(--ds-task-immersive-sidebar),
var(--ds-task-immersive-edge) 100%) !important;
var(--ds-immersive-sidebar),
var(--ds-immersive-edge) 100%) !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
backdrop-filter: none !important;
isolation: isolate;
}

/* The sidebar and main surface share this inherited progress value. Keeping the
stronger task wash in separate overlays avoids a discrete gradient swap. */
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_LEFT_PANEL__::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(90deg,
var(--ds-task-sidebar-overlay),
var(--ds-task-sidebar-edge-overlay));
opacity: var(--ds-route-progress);
will-change: opacity;
}

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_LEFT_PANEL__ > :first-child {
position: relative;
z-index: 1;
}

/* Native sidebar resize chrome inherits the sidebar background and extends
Expand All @@ -334,17 +388,27 @@ 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;
var(--ds-immersive-edge),
var(--ds-immersive-mid) 64%,
var(--ds-immersive-far)) !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: 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__))::before {
content: none;
content: "";
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(90deg,
var(--ds-task-overlay-edge),
var(--ds-task-overlay-mid) 64%,
var(--ds-task-overlay-far));
opacity: var(--ds-route-progress);
will-change: opacity;
}

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__))
Expand Down Expand Up @@ -1084,6 +1148,10 @@ html[data-dream-skin="active"] [data-message-author-role] { border-radius: 16px;
}

@media (prefers-reduced-motion: reduce) {
:root[data-dream-skin="active"] {
--ds-route-transition: 0ms linear;
}

html[data-dream-skin="active"] *,
html[data-dream-skin="active"] *::before,
html[data-dream-skin="active"] *::after {
Expand Down
Loading