Files
Jyotisha/frontend/tests/onboarding-presentation.test.ts
T
Jesse_ChenandClaude Opus 5 62903b7a12
Independent Staging Quality Gate / validate (push) Failing after 9m13s
Independent Staging Quality Gate / publish (push) Skipped
feat(ui): 空状态改成「问候 + 输入框 + 两枚入口 pill」
此前空态是 hero 卡(h1 clamp 到 52px)加两张 min-height 228px 的产品卡,
输入框被压在约 800px 内容之下,主动作不是视觉焦点。

改法:问候拆成 StarterGreeting 留在对话区并底对齐,两个产品入口降级成
StarterEntries 渲染在 composer 之后。composer 本身一行未动——它在空态与
会话态是同一个 DOM 位置,首条消息落地时不重挂载,草稿/焦点/排队卡都不受影响。

顺带修掉:空态 composer 1040px、会话态 760px,发首条消息时输入框会当场
变窄一截。两态统一到 --session-column-width,并新增断言锁住。

校正入口保留三个服务端驱动的文案分支(我第一版收成了两个,是
rectification-agentic-entry 打红才发现)。

DESIGN.md:Product entrypoint card 整节重写为 Starter home;删掉
Suggestion card 死文档(它描述的主题卡样式正是本轮删除的 CSS)。

测试 3350 不变,fail 仍 31 且与基线双向零差异;/ 仍 Static;
CSS gzip 41,096→41,044(−0.13%)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-16 05:31:15 +00:00

67 lines
3.5 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import {
createOnboardingFallbackGreeting,
createStartGreeting,
createStartGreetingParts,
} from "../src/lib/starter-greeting.ts";
import { homeSurface } from "./home-surface.ts";
test("terminal and fallback greetings follow the current profile name", () => {
// 原值: 同时锁 onboardingRequestIdentity / isCurrentOnboardingRequest,避免建议问题串号。
// 新值: 只保留问候语随当前称呼变化。
// 原因: 建议问题请求已删除,身份去重不再有消费点。
const terminalGreeting = createStartGreeting("周宁", new Date(2026, 6, 19, 8), 0);
const fallbackGreeting = createOnboardingFallbackGreeting("周宁");
for (const greeting of [terminalGreeting, fallbackGreeting]) {
assert.match(greeting, /周宁/);
assert.doesNotMatch(greeting, /林遥/);
}
assert.equal(fallbackGreeting, "周宁,从你此刻最关心的问题开始吧。");
});
test("the starter hero splits one time-aware greeting instead of drawing from a separate pool", () => {
// Given: the same daypart variant, asked for as parts and as one line.
const noon = new Date(2026, 6, 19, 12);
const parts = createStartGreetingParts("周宁", noon, 0);
const whole = createStartGreeting("周宁", noon, 0);
// Then: the salutation carries the name and the question carries no name, and they recompose exactly.
assert.equal(parts.salutation, "中午好,周宁。");
assert.equal(parts.question, "现在最想理清哪件事?");
assert.equal(`${parts.salutation}${parts.question}`, whole);
assert.doesNotMatch(parts.question, /周宁/);
// And: the extremes stay inside the variant list rather than falling off the end.
assert.equal(createStartGreetingParts("周宁", noon, 1).question, "事业、关系或选择,想先聊哪个?");
assert.equal(createStartGreetingParts("周宁", noon, 0.999999).question, "事业、关系或选择,想先聊哪个?");
// And: every daypart is reachable, so the heading is not one fixed line in disguise.
const questions = new Set([0, 8, 12, 16, 20, 23].flatMap((hour) =>
[0, 1 / 3, 2 / 3].map((selection) =>
createStartGreetingParts("周宁", new Date(2026, 6, 19, hour), selection).question)));
assert.ok(questions.size >= 12);
});
test("the starter hero stays at a salutation and a question", () => {
const source = homeSurface;
// Given: the hero heading is the question half of the time-aware greeting.
// 原值 `className="starter-greeting"` / 新值 `className="starter-salutation"`
// / 原因:`.starter-greeting` 这个类名此前既指招呼语那一行、又被读成整块问候区,
// 拆成 `.starter-greeting-block`(容器)与 `.starter-salutation`(招呼语)后不再歧义。
// 「两行:招呼语 + 问句」这个意图本身没变。
assert.match(source, /<p className="starter-salutation">\{starterGreeting\.salutation\}<\/p>/);
assert.match(source, /<h1 id="starter-heading">\{starterGreeting\.question\}<\/h1>/);
// Then: no third line repeats the welcome the two lines above already carry.
assert.doesNotMatch(source, /starter-hero-note/);
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
assert.doesNotMatch(styles, /starter-hero-note/);
// And: the separate static heading pool is gone for good.
assert.doesNotMatch(source, /starterPrompt|createStarterPrompt|greetingForHour/);
assert.doesNotMatch(source, /greeting: createStartGreeting\(/);
});