Files
Jyotisha/frontend/src/components/starter-home.tsx
T
Jesse_ChenandClaude Fable 5.1 ff0427cf08
Independent Staging Quality Gate / validate (push) Failing after 6m23s
Independent Staging Quality Gate / publish (push) Skipped
feat(home): 首页开场语改成一行问候,占位符改「想聊什么都可以」
开场语从「称呼行 + 追问句」收成 claude.ai 式的单行问候:时段池、
星期池、回访池按顺序拼成一个数组,按 variantSelection 确定性取一条;
名字为空时跳过带称呼的条目。副标题行与 .starter-salutation 下线,
h1 降到 clamp(24px, 2.8vw, 30px) / line-height 1.25。追问改由输入框
占位符承担。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-17 15:54:20 +00:00

126 lines
5.0 KiB
TypeScript

"use client";
import { Clock3, Sparkles } from "lucide-react";
import type { DailyStarlanguageState } from "@/lib/home-types";
import type { RectificationEntrySummary } from "@/lib/rectification-entry";
/**
* 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 text: string };
};
/* One line. The salutation/question pair it replaces asked the user's question
for them; the composer placeholder asks now. */
export function StarterGreeting({ starterGreeting }: StarterGreetingProps) {
return (
<section className="starter-greeting-block" aria-labelledby="starter-heading">
<h1 id="starter-heading">{starterGreeting.text}</h1>
</section>
);
}
export type StarterEntriesProps = {
readonly dailyStarlanguage: DailyStarlanguageState;
readonly natalMinuteAvailable: boolean;
readonly dailyStarlanguageQuestion: string;
readonly dailyStarlanguageTrend: string;
readonly productEntrypointsDisabled: boolean;
readonly startDailyStarlanguageConsultation: () => void;
readonly rectificationCardLabel: string;
readonly rectificationCardAction: string;
readonly rectificationLoading: boolean;
readonly rectificationMutationPending: boolean;
readonly openRectificationFromHomepage: () => void;
readonly rectificationEntrySummary: RectificationEntrySummary | null;
readonly rectificationError: string;
readonly rectificationSurfaceOpen: boolean;
readonly rectificationErrorMessage: string;
};
export function StarterEntries({
natalMinuteAvailable,
dailyStarlanguage,
dailyStarlanguageQuestion,
dailyStarlanguageTrend,
productEntrypointsDisabled,
startDailyStarlanguageConsultation,
rectificationCardLabel,
rectificationCardAction,
rectificationLoading,
rectificationMutationPending,
openRectificationFromHomepage,
rectificationEntrySummary,
rectificationError,
rectificationSurfaceOpen,
rectificationErrorMessage,
}: 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-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>
);
}