Merge entry overflow rules into the existing 480px breakpoint, restore picker focus without scrolling, and sync composer and entry contracts without changing page logic. Add AST generation-state guard and both picker close-path regressions. Validation: targeted 33/33; Chrome isolated components 145 checks; tsc and lint zero errors. Node22 full suite 3706 tests, no lost names or new failures; three target failures fixed, 88 baseline environment/contract failures unchanged. Windows build remains blocked by symlink EPERM. Changed-file privacy check: 11 files, no findings, not a full-repository pass. BUG-1011 remains separate. Co-Authored-By: Claude Code <noreply@anthropic.com>
43 lines
2.6 KiB
TypeScript
43 lines
2.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const starterHome = readFileSync(new URL("../src/components/starter-home.tsx", import.meta.url), "utf8");
|
|
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
|
const design = readFileSync(new URL("../DESIGN.md", import.meta.url), "utf8");
|
|
|
|
function starterEntryBlock(): string {
|
|
const start = css.indexOf(".starter-entry {");
|
|
const end = css.indexOf(".starter-entry > svg");
|
|
assert.ok(start >= 0 && end > start);
|
|
return css.slice(start, end);
|
|
}
|
|
|
|
test("starter entries stay native buttons with a 36px pill and a 44px hit area", () => {
|
|
assert.match(starterHome, /<button[\s\S]*className="starter-entry"/);
|
|
assert.equal(starterHome.match(/className="starter-entry"/g)?.length, 2);
|
|
assert.doesNotMatch(starterHome, /starter-hero|product-entrypoint|daily-starlanguage-card|birth-rectification-card/);
|
|
assert.doesNotMatch(starterHome, /正在加载|InlineSpinner|animate-spin/);
|
|
const block = starterEntryBlock();
|
|
assert.match(block, /min-height:\s*36px/);
|
|
assert.match(block, /--starter-entry-border:\s*color-mix\(in srgb, var\(--color-border\) 62%, transparent\)/);
|
|
assert.match(block, /border:\s*1px solid var\(--starter-entry-border\)/);
|
|
assert.doesNotMatch(block, /border:\s*1px solid var\(--color-border-strong\)/);
|
|
assert.match(css, /\.starter-entry::after\s*\{[^}]*inset:\s*-4px/);
|
|
});
|
|
|
|
test("starter entry hover stays softer than the action color and focus is a 3px ring", () => {
|
|
const hover = css.slice(css.indexOf(".starter-entry:not(:disabled):hover"), css.indexOf(".starter-entry:focus-visible"));
|
|
assert.match(hover, /color-mix\(in srgb, var\(--color-border-strong\) 45%, var\(--color-border\)\)/);
|
|
assert.doesNotMatch(hover, /border-color:\s*var\(--color-action\)/);
|
|
assert.match(css, /\.starter-entry:focus-visible\s*\{[^}]*outline:\s*3px solid color-mix\(in srgb, var\(--color-focus\) 80%, transparent\)/);
|
|
assert.match(css, /\.starter-entry\[data-opening="true"\]\s*\{\s*cursor:\s*progress/);
|
|
// 原值:入口行 max-width: 100% 位于 max-width: 375px 媒体块。
|
|
// 新值:同一防溢出规则位于既有 max-width: 480px 媒体块。
|
|
// 原因:BUG-1012 按 BUG-697 收敛断点,不扩白名单;按钮尺寸、焦点与防溢出要求不变。
|
|
assert.match(css, /@media \(max-width:\s*480px\)\s*\{[^}]*\.starter-entry-row\s*\{\s*max-width:\s*100%/);
|
|
assert.match(css, /@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*transition-duration:\s*\.01ms !important/);
|
|
assert.match(design, /--starter-entry-border/);
|
|
assert.match(design, /inset: -4px/);
|
|
});
|