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
This commit is contained in:
co-authored by
Claude Opus 5
parent
66d9f59d84
commit
62903b7a12
@@ -1,16 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { Clock3, Sparkles } from "lucide-react";
|
||||
import type { DailyStarlanguageState } from "@/lib/home-types";
|
||||
import type { RectificationEntrySummary } from "@/lib/rectification-entry";
|
||||
|
||||
export type StarterHomeProps = {
|
||||
/**
|
||||
* The empty state is split across two slots on purpose.
|
||||
*
|
||||
* `StarterGreeting` sits in the conversation area, bottom-aligned, so it lands
|
||||
* directly above the composer. `StarterEntries` sits inside `.composer-wrap`,
|
||||
* below the composer. The composer itself never moves: it keeps the one DOM
|
||||
* position it has in a live session, so nothing remounts when the first message
|
||||
* arrives and an in-flight draft, focus or queued card survives the transition.
|
||||
*
|
||||
* Before this, the empty state was a bordered hero card plus two 132px product
|
||||
* cards, and the composer sat under roughly 800px of content — the primary
|
||||
* action was the last thing on the page.
|
||||
*/
|
||||
|
||||
export type StarterGreetingProps = {
|
||||
readonly starterGreeting: { readonly salutation: string; readonly question: string };
|
||||
};
|
||||
|
||||
export function StarterGreeting({ starterGreeting }: StarterGreetingProps) {
|
||||
return (
|
||||
<section className="starter-greeting-block" aria-labelledby="starter-heading">
|
||||
<p className="starter-salutation">{starterGreeting.salutation}</p>
|
||||
<h1 id="starter-heading">{starterGreeting.question}</h1>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export type StarterEntriesProps = {
|
||||
readonly dailyStarlanguage: DailyStarlanguageState;
|
||||
readonly natalMinuteAvailable: boolean;
|
||||
readonly dailyStarlanguageQuestion: string;
|
||||
readonly dailyStarlanguageTrend: string;
|
||||
readonly dailyStarlanguageAction: string;
|
||||
readonly productEntrypointsDisabled: boolean;
|
||||
readonly startDailyStarlanguageConsultation: () => void;
|
||||
readonly rectificationCardLabel: string;
|
||||
@@ -24,13 +49,11 @@ export type StarterHomeProps = {
|
||||
readonly rectificationErrorMessage: string;
|
||||
};
|
||||
|
||||
export function StarterHome({
|
||||
starterGreeting,
|
||||
export function StarterEntries({
|
||||
natalMinuteAvailable,
|
||||
dailyStarlanguage,
|
||||
dailyStarlanguageQuestion,
|
||||
dailyStarlanguageTrend,
|
||||
dailyStarlanguageAction,
|
||||
productEntrypointsDisabled,
|
||||
startDailyStarlanguageConsultation,
|
||||
rectificationCardLabel,
|
||||
@@ -42,69 +65,60 @@ export function StarterHome({
|
||||
rectificationError,
|
||||
rectificationSurfaceOpen,
|
||||
rectificationErrorMessage,
|
||||
}: StarterHomeProps) {
|
||||
}: StarterEntriesProps) {
|
||||
const dailyLabel = natalMinuteAvailable ? "今日星语" : "每日运势";
|
||||
/* The trend line is the only part that arrives late. It stays a live region so
|
||||
a screen reader hears it settle, but it is a tooltip-rank hint now, not a
|
||||
card body — the pill is actionable from the first paint either way. */
|
||||
const dailyHint = natalMinuteAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏";
|
||||
/* Three branches, same as the card it replaces: an unfinished case, a finished
|
||||
one that can be redone, and a first run. The label itself is server-driven
|
||||
from the entry summary (`rectificationCardAction`), not from the session list. */
|
||||
const rectificationHint = rectificationEntrySummary?.hasResumableCase
|
||||
? "未完成的那次可以从左侧继续"
|
||||
: rectificationCardAction === "restart"
|
||||
? "上次已经校正完,可以拿最新资料再来一次"
|
||||
: "不确定出生时间时,用记得住的经历一步步缩小范围";
|
||||
|
||||
return (
|
||||
<div className="starter-list starter-workbench" aria-label="Jyotisha 首页">
|
||||
<section className="starter-hero" aria-labelledby="starter-heading">
|
||||
<div className="starter-hero-copy">
|
||||
<p className="starter-greeting">{starterGreeting.salutation}</p>
|
||||
<h1 id="starter-heading">{starterGreeting.question}</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="product-entrypoints" aria-label="常用占星入口">
|
||||
<article className="daily-starlanguage-card product-entrypoint-card" aria-labelledby="daily-starlanguage-title">
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
aria-label={natalMinuteAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={startDailyStarlanguageConsultation}
|
||||
/>
|
||||
<div className="product-entrypoint-copy">
|
||||
<h2 id="daily-starlanguage-title">{natalMinuteAvailable ? "今日星语" : "每日运势"}</h2>
|
||||
<p
|
||||
role={natalMinuteAvailable && dailyStarlanguage.kind !== "ready" ? "status" : undefined}
|
||||
>{natalMinuteAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}</p>
|
||||
</div>
|
||||
<div className="product-entrypoint-footer">
|
||||
{natalMinuteAvailable
|
||||
? (dailyStarlanguageAction ? <small>{dailyStarlanguageAction}</small> : null)
|
||||
: <small>不支持的个人判断会明确说明,不会补造出生时间。</small>}
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{natalMinuteAvailable ? dailyStarlanguageQuestion : "查看今日运势"} <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
<article
|
||||
className="birth-rectification-card product-entrypoint-card"
|
||||
aria-labelledby="birth-rectification-title"
|
||||
data-opening={rectificationLoading ? "true" : undefined}
|
||||
>
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
aria-label={rectificationCardLabel}
|
||||
disabled={productEntrypointsDisabled || rectificationLoading || rectificationMutationPending}
|
||||
onClick={() => void openRectificationFromHomepage()}
|
||||
/>
|
||||
<div className="product-entrypoint-copy">
|
||||
<h2 id="birth-rectification-title">生时校正</h2>
|
||||
<p>{rectificationEntrySummary?.hasResumableCase
|
||||
? "新建一段独立校正;未完成的记录仍可从左侧历史会话继续。"
|
||||
: rectificationCardAction === "restart"
|
||||
? "上一次校正已经完成,可以基于最新资料再次校正。"
|
||||
: "不确定准确出生时间时,可通过已经发生的人生事件逐步缩小范围。"}</p>
|
||||
</div>
|
||||
<div className="product-entrypoint-footer">
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{rectificationCardLabel} <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
{rectificationError && !rectificationSurfaceOpen && (
|
||||
<p className="form-error rectification-entry-error" role="alert">
|
||||
{rectificationErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="starter-entries">
|
||||
<div className="starter-entry-row">
|
||||
<button
|
||||
className="starter-entry"
|
||||
type="button"
|
||||
aria-label={natalMinuteAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={startDailyStarlanguageConsultation}
|
||||
>
|
||||
<Sparkles aria-hidden="true" />
|
||||
<span>{dailyLabel}</span>
|
||||
</button>
|
||||
<button
|
||||
className="starter-entry"
|
||||
type="button"
|
||||
aria-label={rectificationCardLabel}
|
||||
data-opening={rectificationLoading ? "true" : undefined}
|
||||
disabled={productEntrypointsDisabled || rectificationLoading || rectificationMutationPending}
|
||||
onClick={() => void openRectificationFromHomepage()}
|
||||
>
|
||||
<Clock3 aria-hidden="true" />
|
||||
<span>生时校正</span>
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className="starter-entry-hint"
|
||||
role={natalMinuteAvailable && dailyStarlanguage.kind !== "ready" ? "status" : undefined}
|
||||
>
|
||||
{dailyHint} · {rectificationHint}
|
||||
</p>
|
||||
{!natalMinuteAvailable && (
|
||||
<p className="starter-entry-boundary">出生时间不足以支持的判断我会说明,不会补造具体分钟。</p>
|
||||
)}
|
||||
{rectificationError && !rectificationSurfaceOpen && (
|
||||
<p className="form-error rectification-entry-error" role="alert">
|
||||
{rectificationErrorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user