diff --git a/AGENTS.md b/AGENTS.md index 082fe7c..484cfe4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,7 @@ If Astro ships TS 7 support, wire `astro check` back in rather than leaving this - **Preserve the size algebra.** `cell = size / ((n - 1)(1 + gap) + dotSize)` is solved, not clipped, which is what keeps the field landing exactly on the box at any `dotSize` — including above 1, where the dots touch. If you change it, the invariant tests in `packages/core/test/dot-matrix.test.ts` are the specification. +- **Verify layout in a browser, not in jsdom.** jsdom computes no layout at all — `getComputedStyle(el).gridTemplateColumns` on a real grid rule returns the empty string — so no unit test here can catch a box that has collapsed. A stage whose controls column was an `auto` track (sized by max-content) grew from 264px to 602px on a single long caption and squeezed the field's track to zero; the stage is `overflow: hidden`, so the field was clipped out of existence while the DOM still held all 25 cells and every source-level check passed. Drive Chrome over CDP (`--remote-debugging-port`, `Runtime.evaluate`, `Page.captureScreenshot`) with a **dedicated `--user-data-dir`** — reusing the default one makes the instance exit immediately — and assert on measured geometry and on screenshots. Regexing the stylesheet for the rule is not a substitute: a test written that way matched a different component's `repeat(auto-fill, …)` and stayed green with the bug present. - **Direction is per-order AXES, not a global "reverse".** `direction` is a list of axis tokens (`["counterClockwise", "insideOut"]`), and each order reads only its own — `ORDER_DIRECTION_AXES` is the contract a UI reads to know what to offer. Offer from that table and never from a hardcoded list: a diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a7e52..499a564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,9 @@ ### Fixed +- **Fixed: `Column Drop` still showed nothing, and this time the field was being clipped rather than missing.** The previous fix made the motion render, and it _was_ rendering — 25 cells, animating, one column lit at a time. The stage's controls column was an `auto` track, and `auto` is sized by max-content, so the refusal caption ("this motion is a highlight moving down the field…", shown only when the CSS renderer refuses the chosen motion) grew that track from **264px to 602px** and squeezed the field's own track to **zero**. The stage is `overflow: hidden`, so the field was not squashed — it was clipped out of existence. The tracks are capped now, and the caption wraps inside the cap instead of widening it. +- **This one has no automated test, on purpose.** jsdom computes no layout, so no test in this repo can catch a collapsed track, and the first attempt at one was worse than nothing: it regexed the stylesheet, matched a **different** component's `repeat(auto-fill, …)`, and stayed green with the bug present. A test that asserts about the wrong rule is not a slow test, it is a wrong one. `AGENTS.md` now records that this class of check needs a real browser over CDP, with the `--user-data-dir` detail that cost the most time to work out. + - **Fixed: picking `Column Drop` left the field blank.** A motion the CSS renderer cannot express — one that MOVES a highlight down a column, which a per-dot `animation-delay` cannot do — was still being rendered through it, and `buildCss` returns null for exactly those. The playground disabled the CSS switch and said so, then passed `renderer="css"` anyway, so the stage came up **empty**. Disabling a control without honouring it is the worst of both answers: the switch explains itself while the field shows nothing. The field now renders the one way it can, and the readout and the copied snippet say `svg` rather than claiming a mode the engine is not in. The engine normalises the same thing for direct callers, so `handle.options.renderer` reports what is actually painted instead of leaving the element empty. - **The CSS renderer no longer publishes keyframes it cannot honour.** It emitted `@keyframes botui-dm-columns`, and that rule animated _nothing_: `spike` is a gaussian centred on the cycle seam, so sampling it at the two positions its stop table names — 0 and 1 — yields full brightness at both ends, i.e. a static fully-lit field. Shipping that in every consumer's stylesheet was a lie about what the motion looks like, not merely dead weight. Only presets the renderer actually accepts get a rule, which is the same rule `buildCss` applies. diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index 35b4240..537f968 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -51,6 +51,9 @@ ### 修复 +- **修复:`Column Drop` 仍然什么都看不到,而这次场是被裁掉了而不是缺失。** 上一个修复让这个动效真的渲染出来了,而且它**确实**在渲染 —— 25 个格子、在动、每次点亮一列。舞台的控件列是 `auto` 轨道,而 `auto` 按 max-content 定尺寸,于是那条拒绝提示("这个动效是高亮沿着场移动…" —— 只在 CSS 渲染器拒绝当前动效时显示)把该轨道从 **264px 撑到 602px**,把场自己的轨道挤到 **0**。舞台是 `overflow: hidden`,所以场不是被压扁,而是被整个裁掉。轨道现在设了上限,提示在上限内换行,而不是把列撑宽。 +- **这一条故意没有自动化测试。** jsdom 完全不算布局,所以本仓库里没有任何测试能抓住塌掉的轨道;而我写的第一版比没有更糟:它用正则去匹配样式表,匹配到了**另一个**组件的 `repeat(auto-fill, …)`,于是在 bug 存在时依然全绿。能断言到错误规则的测试不是慢的测试,是错的测试。`AGENTS.md` 现在记下了这类检查需要用 CDP 驱动真实浏览器,并写下了最耗时间才弄明白的 `--user-data-dir` 细节。 + - **修复:选中 `Column Drop` 后场是空白的。** CSS 渲染器无法表达的动效 —— 那种把高亮**移动**到下一列的动效,而每格 `animation-delay` 做不到这件事 —— 仍然被交给它渲染,而 `buildCss` 对这些 preset 恰好返回 null。Playground 把 CSS 开关禁用了并说明了原因,**然后照样传 `renderer="css"`**,于是舞台**什么都没有**。禁用一个控件却不遵守它是最糟的答案:开关解释了自己,而场什么也不显示。现在场会用它唯一能的方式渲染,readout 和复制出的片段都如实写 `svg`,而不是声称一个引擎并未处于的模式。引擎侧对直接调用方做同样的归一化,于是 `handle.options.renderer` 报告真正被绘制的东西,而不是留一个空元素。 - **CSS 渲染器不再发布自己无法兑现的 keyframes。** 它一直在输出 `@keyframes botui-dm-columns`,而那条规则**什么都不动**:`spike` 是一个以周期接缝为中心的 gaussian,所以按它的 stop 表所列的两个位置 —— 0 和 1 —— 采样,两端都是全亮度,也就是一块静止的全亮场。把这个规则塞进每个使用者的样式表里,是对那个动效外观的**谎称**,而不只是无用代码。现在只有渲染器真正接受的 preset 才有规则,与 `buildCss` 用的是同一条判据。 diff --git a/apps/site/src/styles/site.css b/apps/site/src/styles/site.css index ebaf3d6..9db479b 100644 --- a/apps/site/src/styles/site.css +++ b/apps/site/src/styles/site.css @@ -304,6 +304,15 @@ h3 { grid-template-columns: 1fr; gap: 6px; font-size: 13px; + /* without this the caption's intrinsic width is what the track is sized from */ + min-width: 0; +} + +.stage-toggle-label em { + /* the refusal caption is a sentence, not a control label: let it wrap inside the + controls column instead of pushing the column wider */ + display: block; + line-height: 1.5; } .stage-controls label { @@ -632,7 +641,16 @@ footer { .playground-stage { display: grid; - grid-template-columns: auto minmax(0, 1fr); + /* Bounded, not `auto`. The controls column holds a sentence — "this motion is a + highlight moving down the field…" — and an `auto` track is sized by max-content, so + that one caption grew the track from 264px to 602px and squeezed the field's own track + to ZERO. The stage has `overflow: hidden`, so the field was not squashed, it was + clipped out of existence: the DOM had all 25 cells and the page showed nothing. + + An `auto` track cannot express "as wide as it needs, but never at the field's + expense", and `minmax(0, auto)` is the same thing. The track is capped instead, and + `min-width: 0` lets the caption wrap inside that cap rather than widening it. */ + grid-template-columns: minmax(0, 264px) minmax(0, 1fr); border: 1px solid var(--line); border-radius: var(--radius); background: var(--card);