BUG-737:--font-display 以两个从未加载的 Anthropic 授权字体开头 (Tiempos Headline / StyreneB:无 @font-face、public/ 无字体、layout.tsx 只 vendor 了 Inter),于是每个中文标题都掉到 Songti SC / SimSun——20 条 规则宽,含助手回答正文的 h2/h3。display 与 body 合并为同一条无衬线栈, 层级改由字重承担(33 条 display 规则 400→500)。 拉丁衬线方案实测否决:Newsreader 拉丁子集 132 KB(Inter 的 2.7 倍), 只为给一个品牌字上衬线,且会把「D10 事业盘怎么读」劈成两种字形。 BUG-738:亮色 #85432f 与暗色 #d78064 不同源;--color-ring 在 @theme inline 里硬编码不跟随 :root。产品拍板换 Claude coral #cc785c, 但实测它作文字色只有 3.14:1,而 62 个调用点里 53 个是 color:。 按角色拆两阶:--color-action #a9583e(文字 4.85:1)、 --color-action-strong #cc785c(填充/导轨/焦点环,3:1 非文字阈值)。 --report-accent 刻意保持 #85432f(报告是纸面,不跟应用强调色)。 T1.3/T1.4:模型选择器进 ChatComposer 的 toolbar 插槽,删掉常驻 44px 的 .composer-footer;顶栏 68→46px,「分析对象:」改为标题旁静默 chip。 防复发:新增 font-stack-loadable-contract,断言字体栈里每个 family 要么 vendored 要么是系统字体,且 --font-display 不得触达任何 CJK 衬线。 测试 3346→3350,fail 仍 31 且清单与基线逐条一致;/ 仍 Static; CSS gzip 41,095→41,096(+0.002%);快速门 pytest 段 798 passed / 0 failed。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
57 lines
4.2 KiB
TypeScript
57 lines
4.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
const readProjectFile = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
|
|
const globalStyles = readProjectFile("src/app/globals.css");
|
|
const messageRowSource = readProjectFile("src/components/chat-message-row.tsx");
|
|
|
|
test("aligns the session transcript and composer to one readable column", () => {
|
|
assert.match(globalStyles, /--session-column-width:\s*760px/);
|
|
assert.match(globalStyles, /\.conversation:not\(\.is-empty\):not\(\.is-rectification\) \.message-list\s*\{[^}]*width:\s*min\(calc\(var\(--session-column-width\)/);
|
|
assert.match(globalStyles, /\.conversation:not\(\.is-empty\):not\(\.is-rectification\) \+ \.composer-wrap \.composer[^\{]*\{[^}]*width:\s*min\(var\(--session-column-width\),\s*100%\)/);
|
|
// 原值:`.composer-footer` 与 `.composer` 同宽 / 新值:只剩 `.composer`(上一条已断言)
|
|
// / 原因:常驻底栏已删,工具行移进输入框内部,不再有第二个需要对齐的盒子。
|
|
assert.doesNotMatch(globalStyles, /\.composer-footer\s*\{/);
|
|
});
|
|
|
|
test("keeps onboarding transcript and intake card on the same session column", () => {
|
|
const pageSource = readProjectFile("src/app/page.tsx");
|
|
assert.match(pageSource, /\$\{!profileComplete \? " is-onboarding" : ""\}/);
|
|
assert.match(globalStyles, /\.conversation\.is-onboarding \.welcome \{[\s\S]*width:\s*min\(calc\(var\(--session-column-width\) \+ \(var\(--session-column-gutter\) \* 2\)\), 100%\)/);
|
|
assert.match(globalStyles, /\.conversation\.is-onboarding\.is-empty \.welcome \{[\s\S]*width:\s*min\(var\(--session-column-width\), 100%\)/);
|
|
assert.match(globalStyles, /\.conversation\.is-onboarding \.onboarding-card,\s*\n\.conversation\.is-onboarding \.onboarding-card\.birth-time-transition-card \{[\s\S]*width:\s*100%;[\s\S]*max-width:\s*none/);
|
|
assert.match(globalStyles, /\.conversation\.is-onboarding-form \.welcome \{[\s\S]*padding:\s*var\(--space-6\) var\(--session-column-gutter\) var\(--space-8\)/);
|
|
});
|
|
|
|
test("centers the starter home even when the conversation grid is not is-empty", () => {
|
|
const pageSource = readProjectFile("src/app/page.tsx");
|
|
assert.match(globalStyles, /\.welcome \{\s*width:\s*min\(1040px, 100%\);\s*margin-inline:\s*auto;/);
|
|
assert.match(globalStyles, /\.starter-list \{\s*width:\s*min\(1040px, 100%\);\s*margin-inline:\s*auto;/);
|
|
assert.match(
|
|
pageSource,
|
|
/!onboardingFormActive && !activeSession\?\.messages\.length && \(Boolean\(activeSession\?\.messagesHydrated\) \|\| activeSession\?\.sessionType !== "consultation"\) \? " is-empty"/,
|
|
);
|
|
});
|
|
|
|
test("keeps message motion restrained and honors reduced-motion preferences", () => {
|
|
assert.match(messageRowSource, /gsap\.matchMedia\(\)/);
|
|
assert.match(messageRowSource, /prefers-reduced-motion:\s*no-preference/);
|
|
// Former value: `duration:\s*0\.18`. DESIGN.md §6 gives the message entrance 160ms; 0.18 was
|
|
// the GSAP copy drifting from the (now removed) 160ms CSS keyframe that ran alongside it.
|
|
assert.match(messageRowSource, /duration:\s*0\.16/);
|
|
assert.match(messageRowSource, /ease:\s*"cubic-bezier\(\.22,\s*1,\s*\.36,\s*1\)"/);
|
|
assert.match(messageRowSource, /clearProps:\s*"opacity,transform,visibility"/);
|
|
});
|
|
|
|
test("lets model controls fit their labels and keeps the stop action on-brand", () => {
|
|
assert.match(globalStyles, /\.model-selector-trigger\s*\{[^}]*width:\s*fit-content/);
|
|
assert.match(globalStyles, /\.model-selector-popup\s*\{[^}]*width:\s*max-content[^}]*min-width:\s*180px[^}]*max-width:\s*min\(420px,/);
|
|
assert.match(globalStyles, /\.model-selector-copy b\s*\{[^}]*overflow:\s*visible[^}]*white-space:\s*normal/);
|
|
assert.doesNotMatch(globalStyles, /\.model-selector-copy b\s*\{[^}]*text-overflow:\s*ellipsis/);
|
|
// 原值 `var(--color-action)` / 新值 `var(--color-action-strong)` / 原因:停止键是填充面,
|
|
// 走品牌 coral 那一阶;文字阶留给 --color-action。见 BUG-738。
|
|
assert.match(globalStyles, /\.composer \.composer-stop\s*\{[^}]*background:\s*var\(--color-action-strong\)/);
|
|
assert.match(globalStyles, /\.composer \.composer-stop:not\(:disabled\):hover\s*\{[^}]*background:\s*var\(--color-action-hover\)/);
|
|
});
|