主题切换的圆形展开 VT 动画对可运行代码块不生效:圆形扫过 CodeMirror/xterm
区域时看不到颜色变化,动画结束后才瞬切。
根因(逐帧像素验证确认):VT 回调只 toggle .dark class,但 CodeMirror 背景
由 catppuccin Extension 注入(.cm-editor 的 EditorView.theme 规则),xterm
背景由 .xterm-scrollable-element 的 inline background-color 注入——两者都
不随 .dark 翻转。set_theme 由 Dioxus use_effect 在 theme.set() 之后异步触发,
晚于 NEW 快照捕获,导致 OLD/NEW 快照在编辑器区域同色,圆形展开无可揭示的变化。
修复:在 VT 回调内(NEW 快照捕获前)同步 dispatch 'yggdrasil:theme-change'
CustomEvent,CodeMirror/xterm 各自的 index.ts 在模块加载时订阅并遍历
_instances 同步调 setTheme。事件先于 applyDarkClass dispatch,确保编辑器换肤
+ class 翻转被同一个 getComputedStyle reflow 捕获进 NEW 快照。
与现有 Dioxus use_effect 幂等共存:setTheme 对相同主题是 no-op(CodeMirror
Compartment.reconfigure + xterm options.theme = 均幂等),use_effect 作兜底
覆盖初始挂载 / 非 VT 场景。
验证:
- yggdrasil-core 测试扩展:断言 VT 回调 / 降级路径 / applyResolvedTheme 都
dispatch 事件,且事件先于 dark class 翻转
- scripts/vt-theme-sampler.mjs(Playwright 逐帧像素采样):修复前 xterm 比
等距的 cssvar 探针晚 100ms 变色(bug);修复后同帧变色(lag=0ms)
改动文件:
- libs/yggdrasil-core/src/theme-transition.ts: THEME_CHANGE_EVENT 常量 +
notifyThemeChange 辅助函数,VT 回调 / 降级路径 / applyResolvedTheme 三处 dispatch
- libs/codemirror-editor/src/index.ts: 订阅事件,forEach _instances.setTheme
- libs/xterm-terminal/src/index.ts: 同样模式
- libs/yggdrasil-core/src/theme-transition.test.ts: 3 个新用例
- public/xterm/terminal.js: 重建产物(含事件订阅)
- scripts/vt-theme-{harness,sampler}: 逐帧像素验证工具(throwaway,可作回归)
148 lines
6.3 KiB
HTML
148 lines
6.3 KiB
HTML
<!doctype html>
|
||
<!--
|
||
VT 主题切换动画验证 harness —— 逐帧像素采样。
|
||
|
||
目的:验证「暗/亮切换的圆形展开 VT 动画」是否对可运行代码块的
|
||
xterm 终端区域生效。预期 bug:VT 动画期间(0.4s),xterm canvas 背景仍是
|
||
旧色(因为 set_theme 在 VT 回调之后才由 Dioxus use_effect 触发,而 NEW 快照
|
||
在回调里就拍好了),圆形扫过时终端区域「不动」,动画结束后才瞬切。
|
||
|
||
本 harness 加载项目真实的构建产物(不做任何 JS 改写),用真实的
|
||
yggdrasil-core __startThemeTransition + 真实的 XtermTerminal.create +
|
||
真实的 terminal.css。额外放三个采样探针 div,由 Playwright 按坐标读色:
|
||
|
||
- body-probe : 背景 = var(--color-paper-theme) (随 .dark 翻转)
|
||
- cssvar-probe : 背景 = var(--color-paper-code-block) (随 .dark 翻转)
|
||
- xterm-mount : XtermTerminal.create 挂载点,canvas 渲染 (命令式 set_theme)
|
||
|
||
关键:模拟真实时序——
|
||
1. __startThemeTransition(x, y) ← 同步:截 OLD → 回调翻 .dark → 截 NEW
|
||
2. 之后(用 setTimeout 模拟 Dioxus use_effect 的延迟)调 set_theme('dark')
|
||
这样 VT 的 NEW 快照里 xterm 仍是旧色,而 body/cssvar(随 CSS 变量)已是新色。
|
||
圆形展开用 body/cssvar 的 NEW 覆盖 OLD;但 xterm 的 NEW 快照=旧色,故圆形
|
||
扫过 xterm 区域时看不到变化——直到 set_theme 跑完才瞬切。
|
||
|
||
探针布局:toggle 原点在左侧,三个探针排成一行在右侧,使圆形从左向右扫过,
|
||
便于观察时序差异(理想:三探针同时变;bug 态:xterm 滞后)。
|
||
-->
|
||
<html lang="zh">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>VT theme harness</title>
|
||
<link rel="stylesheet" href="/yggdrasil-core/yggdrasil-core.css" />
|
||
<link rel="stylesheet" href="/xterm/terminal.css" />
|
||
<style>
|
||
/* ===== 复刻 input.css 里与本次验证相关的变量定义 =====
|
||
只抄验证需要的两对变量(theme / code-block),不引整个 input.css。 */
|
||
:root {
|
||
--color-paper-theme: #eff1f5; /* input.css light, Latte Base */
|
||
--color-paper-code-block: #dce0e8; /* input.css light, Latte Surface0 */
|
||
}
|
||
.dark {
|
||
--color-paper-theme: #1e1e2e; /* input.css dark, Mocha Base */
|
||
--color-paper-code-block: #313244;/* input.css dark, Mocha Surface0 */
|
||
}
|
||
html, body { margin: 0; padding: 0; }
|
||
body {
|
||
background: var(--color-paper-theme);
|
||
color: #4c4f69;
|
||
font-family: ui-monospace, monospace;
|
||
transition: none;
|
||
}
|
||
/* 三个探针:固定大小 60×60,采样中心像素。
|
||
关键布局:三个探针放在同一垂直列(top 相同),水平紧邻——使它们到 toggle
|
||
原点的距离几乎相同,圆形 clip-path 同时覆盖三者。这样任何「xterm 比 body 晚变色」
|
||
的 lag 都只能来自快照不同步(真 bug),而非几何距离差(假阳性)。 */
|
||
.probe {
|
||
position: fixed;
|
||
top: 120px;
|
||
width: 60px;
|
||
height: 60px;
|
||
border: 1px solid rgba(0,0,0,0.15);
|
||
}
|
||
#body-probe {
|
||
left: 200px;
|
||
background: var(--color-paper-theme);
|
||
}
|
||
#cssvar-probe {
|
||
left: 270px;
|
||
background: var(--color-paper-code-block);
|
||
}
|
||
#xterm-mount {
|
||
left: 340px;
|
||
/* 容器底色与生产一致(runner.rs 输出区 div 用 var(--color-paper-code-block)),
|
||
xterm canvas 画在其上。采样点取 canvas 中心,读的是 xterm 画的背景色。 */
|
||
background: var(--color-paper-code-block);
|
||
}
|
||
#xterm-mount .xterm { height: 100%; }
|
||
#xterm-mount .xterm-viewport { height: 100% !important; }
|
||
.label {
|
||
position: fixed; top: 10px; font-size: 10px;
|
||
color: #888; pointer-events: none; white-space: nowrap;
|
||
}
|
||
#toggle-btn {
|
||
position: fixed; left: 40px; top: 40px;
|
||
padding: 12px 20px; font-size: 14px; cursor: pointer;
|
||
background: #fff; border: 1px solid #999;
|
||
}
|
||
#status {
|
||
position: fixed; left: 40px; top: 90px;
|
||
font-size: 12px; color: #555;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="">
|
||
<button id="toggle-btn" type="button">toggle theme</button>
|
||
<div id="status">ready</div>
|
||
|
||
<div class="label" style="left:200px">body-probe</div>
|
||
<div class="label" style="left:270px">cssvar-probe</div>
|
||
<div class="label" style="left:340px">xterm-mount</div>
|
||
|
||
<div id="body-probe" class="probe"></div>
|
||
<div id="cssvar-probe" class="probe"></div>
|
||
<div id="xterm-mount" class="probe"></div>
|
||
|
||
<script src="/yggdrasil-core/yggdrasil-core.js"></script>
|
||
<script src="/xterm/terminal.js"></script>
|
||
<script>
|
||
// 挂载真实 xterm 实例,写几行字让 canvas 有可见像素。
|
||
// XtermOptions 构建产物里是空 class;直接赋值 JS 属性(theme/fontSize/onReady)。
|
||
// create 直接返回 TerminalInstance;onReady 在构造函数末尾同步触发(create 返回前),
|
||
// 故 onReady 只置标志,返回后再写内容。
|
||
window.__harnessReady = false;
|
||
(function () {
|
||
var opts = new window.XtermOptions();
|
||
opts.theme = 'light';
|
||
opts.fontSize = 13;
|
||
opts.onReady = function () { window.__xtermConstructed = true; };
|
||
var inst = window.XtermTerminal.create('xterm-mount', opts);
|
||
window.__harnessTerm = inst;
|
||
if (inst) {
|
||
inst.writeAll('hello\nline2\nline3', '');
|
||
window.__harnessReady = true;
|
||
document.getElementById('status').textContent = 'xterm mounted';
|
||
}
|
||
})();
|
||
|
||
// toggle:模拟真实 ThemeToggle::onclick 时序。
|
||
// __startThemeTransition(x, y) ← VT:截OLD → 回调(翻.dark + dispatch 事件) → 截NEW
|
||
//
|
||
// 修复后,CodeMirror/xterm 的 set_theme 由 VT 回调内的 'yggdrasil:theme-change'
|
||
// 事件同步触发(在 NEW 快照捕获前),不再需要外部 setTimeout 模拟 Dioxus use_effect。
|
||
// 真实 Dioxus use_effect 仍会跑(兜底),但本 harness 不模拟它——只验证 VT 事件路径
|
||
// 是否让 xterm 在快照前换肤(这才是修复的核心)。
|
||
//
|
||
// 原点放在按钮中心(左上),圆形向右展开扫过三个探针。
|
||
document.getElementById('toggle-btn').addEventListener('click', function (e) {
|
||
var fn = window.__startThemeTransition;
|
||
if (typeof fn === 'function') {
|
||
fn(e.clientX, e.clientY);
|
||
} else {
|
||
document.documentElement.classList.toggle('dark');
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|