- D2 resumable hint becomes「上次那次校正还没完成,可以在历史对话里接着做。」 so it does not imply the homepage pill resumes (it always creates). - The click-time non-self error in useRectificationSurface is the same user-visible sentence as the homepage hint; add the full stop. - Source, test, VOICE, DESIGN, CHANGELOG, BUG-1041, testing list and PROGRESS (amendment by Claude) updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
132 lines
5.3 KiB
TypeScript
132 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import { Clock3, MoonStar } from "lucide-react";
|
|
import { useCurrentSubject } from "@/lib/current-subject";
|
|
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 };
|
|
/* Today's trend, one quiet line under the greeting. `null` hides it (no natal
|
|
minute). Before the card is ready the caller passes the static sentence,
|
|
never a placeholder or a spinner. */
|
|
readonly dailyTrend?: string | null;
|
|
readonly dailyTrendSettled?: boolean;
|
|
};
|
|
|
|
/* One line. The salutation/question pair it replaces asked the user's question
|
|
for them; the composer placeholder asks now. The optional sub-line is today's
|
|
trend, moved up from the fine print under the entry pills where it was glued
|
|
to an unrelated rectification hint with a middle dot. */
|
|
export function StarterGreeting({ starterGreeting, dailyTrend = null, dailyTrendSettled = true }: StarterGreetingProps) {
|
|
return (
|
|
<section className="starter-greeting-block" aria-labelledby="starter-heading">
|
|
<h1 id="starter-heading">{starterGreeting.text}</h1>
|
|
{dailyTrend ? (
|
|
<p className="starter-greeting-sub" role={dailyTrendSettled ? undefined : "status"}>
|
|
{dailyTrend}
|
|
</p>
|
|
) : null}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* The line under the pills only appears when there is something to do about
|
|
it. First runs and finished cases say nothing: the pill already names the
|
|
action. The branch reads the entry summary itself, not the derived
|
|
start/restart label, and a non-self subject wins because the case summary
|
|
belongs to the account owner. */
|
|
export function rectificationEntryHint(
|
|
rectificationForSelf: boolean,
|
|
summary: RectificationEntrySummary | null,
|
|
): string | null {
|
|
if (!rectificationForSelf) return "生时校正暂时只支持本人。";
|
|
if (summary?.hasResumableCase) return "上次那次校正还没完成,可以在历史对话里接着做。";
|
|
return null;
|
|
}
|
|
|
|
export type StarterEntriesProps = {
|
|
readonly natalMinuteAvailable: boolean;
|
|
readonly dailyStarlanguageQuestion: string;
|
|
readonly productEntrypointsDisabled: boolean;
|
|
readonly startDailyStarlanguageConsultation: () => void;
|
|
readonly rectificationCardLabel: 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,
|
|
dailyStarlanguageQuestion,
|
|
productEntrypointsDisabled,
|
|
startDailyStarlanguageConsultation,
|
|
rectificationCardLabel,
|
|
rectificationLoading,
|
|
rectificationMutationPending,
|
|
openRectificationFromHomepage,
|
|
rectificationEntrySummary,
|
|
rectificationError,
|
|
rectificationSurfaceOpen,
|
|
rectificationErrorMessage,
|
|
}: StarterEntriesProps) {
|
|
const subjectId = useCurrentSubject();
|
|
const rectificationForSelf = subjectId === "self";
|
|
const dailyLabel = natalMinuteAvailable ? "今日星语" : "每日运势";
|
|
const rectificationHint = rectificationEntryHint(rectificationForSelf, rectificationEntrySummary);
|
|
|
|
return (
|
|
<div className="starter-entries">
|
|
<div className="starter-entry-row">
|
|
<button
|
|
className="starter-entry"
|
|
type="button"
|
|
aria-label={natalMinuteAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
|
|
disabled={productEntrypointsDisabled}
|
|
onClick={startDailyStarlanguageConsultation}
|
|
>
|
|
<MoonStar 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 || !rectificationForSelf}
|
|
onClick={() => void openRectificationFromHomepage()}
|
|
>
|
|
<Clock3 aria-hidden="true" />
|
|
<span>生时校正</span>
|
|
</button>
|
|
</div>
|
|
{rectificationHint && <p className="starter-entry-hint">{rectificationHint}</p>}
|
|
{!natalMinuteAvailable && (
|
|
<p className="starter-entry-boundary">出生时间不足以支持的判断我会说明,不会补造具体分钟。</p>
|
|
)}
|
|
{rectificationError && !rectificationSurfaceOpen && (
|
|
<p className="form-error rectification-entry-error" role="alert">
|
|
{rectificationErrorMessage}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|