fix(chat): fold natal reports and drop homepage topic cards
Independent Staging Quality Gate / validate (push) Failing after 9m47s
Independent Staging Quality Gate / publish (push) Skipped

Homepage no longer waits on /api/onboarding for six starter questions.
Natal answers show the spoken layer first; the Level 2 skeleton sits in a
collapsed 完整分析 block. Wide tables scroll sideways on a phone. Form
inputs are 16px so iOS does not zoom on focus.
This commit is contained in:
jesse-ux
2026-09-15 12:27:16 +08:00
parent 899f955d4b
commit 8144fca27d
46 changed files with 966 additions and 2303 deletions
@@ -0,0 +1,25 @@
"use client";
import type { ReactNode } from "react";
import { answerDetailCaption } from "@/lib/chat-answer-split";
export function AnswerDetailDisclosure({
headings,
streaming = false,
children,
}: {
readonly headings: readonly string[];
readonly streaming?: boolean;
readonly children?: ReactNode;
}) {
return (
<details className="answer-detail">
<summary>
<small>{streaming ? "正在写…" : answerDetailCaption(headings)}</small>
</summary>
{streaming ? null : <div className="answer-detail-panel">{children}</div>}
</details>
);
}
@@ -2,9 +2,11 @@
import { memo, useEffect, useState, type ReactNode } from "react";
import { AnswerDetailDisclosure } from "@/components/chat-answer-detail";
import { prefetchOnIdle } from "@/components/chat-chunk-prefetch";
import { plainParagraphs } from "@/components/chat-message-paragraphs";
import { TechniqueAuditDisclosure } from "@/components/technique-audit-disclosure";
import { splitSpokenAndReport } from "@/lib/chat-answer-split";
import { splitStableMarkdown } from "@/lib/chat-markdown-split";
import type { TechniqueAuditRow } from "@/lib/consultation-agent-events";
import {
@@ -94,8 +96,19 @@ export function ChatMessageContent({
const renderMarkdown = useMarkdownRenderer();
const split = splitSpokenAnswerAndTechniqueAudit(text);
const rows = resolveTechniqueAuditRows(split, auditRows);
const spoken = split.spoken;
const layers = splitSpokenAndReport(split.spoken);
const spoken = layers.spoken;
const report = layers.report;
const showFoldedAudit = rows.length > 0 && split.rows.length === 0;
const foldedAudit = showFoldedAudit ? <TechniqueAuditDisclosure rows={rows} /> : null;
const reportBody = !streaming && report
? (
<>
<div className="message-markdown">{renderProse(report, renderMarkdown)}</div>
{foldedAudit}
</>
)
: null;
return (
<div className="message-answer">
@@ -103,14 +116,16 @@ export function ChatMessageContent({
<div className="message-markdown">
{streaming
? <StreamingMarkdown text={spoken} renderMarkdown={renderMarkdown} />
: renderMarkdown
? renderMarkdown(spoken)
: (plainParagraphs(spoken) ?? []).map((paragraph, index) => (
<p key={index}>{paragraph}</p>
))}
: renderProse(spoken, renderMarkdown)}
</div>
) : null}
{showFoldedAudit ? <TechniqueAuditDisclosure rows={rows} /> : null}
{report
? (
<AnswerDetailDisclosure headings={layers.headings} streaming={streaming}>
{reportBody}
</AnswerDetailDisclosure>
)
: foldedAudit}
</div>
);
}
+2 -57
View File
@@ -1,21 +1,9 @@
"use client";
import { ArrowUpRight } from "lucide-react";
import type { ConsultationEntrypoint } from "@/lib/consultation-entrypoint";
import type { DailyStarlanguageState, Theme } from "@/lib/home-types";
import type { DailyStarlanguageState } from "@/lib/home-types";
import type { RectificationEntrySummary } from "@/lib/rectification-entry";
export type StarterHomeTheme = {
readonly id: string;
readonly label: string;
readonly prompt: string;
};
export type StarterHomeSuggestion = {
readonly theme: Theme;
readonly text: string;
};
export type StarterHomeProps = {
readonly starterGreeting: { readonly salutation: string; readonly question: string };
readonly dailyStarlanguage: DailyStarlanguageState;
@@ -34,14 +22,6 @@ export type StarterHomeProps = {
readonly rectificationError: string;
readonly rectificationSurfaceOpen: boolean;
readonly rectificationErrorMessage: string;
readonly starterThemes: readonly StarterHomeTheme[];
readonly starterSuggestions: readonly StarterHomeSuggestion[];
readonly startSuggestedConsultation: (
text: string,
theme: Theme,
entrypoint?: ConsultationEntrypoint | null,
) => void;
readonly onboardingError: string;
};
export function StarterHome({
@@ -62,13 +42,9 @@ export function StarterHome({
rectificationError,
rectificationSurfaceOpen,
rectificationErrorMessage,
starterThemes,
starterSuggestions,
startSuggestedConsultation,
onboardingError,
}: StarterHomeProps) {
return (
<div className="starter-list starter-workbench" aria-label="Jyotisha 推荐的初始问题">
<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>
@@ -128,37 +104,6 @@ export function StarterHome({
{rectificationErrorMessage}
</p>
)}
<section className="starter-themes" aria-labelledby="starter-themes-heading">
<div className="starter-section-heading">
<h2 id="starter-themes-heading"></h2>
<p>{natalMinuteAvailable
? "选择一个你现在想解决的问题。"
: "选择一个你现在想解决的问题;出生时间不足以支持的部分,我会明确说明,不会补造具体分钟。"}</p>
</div>
<div className="starter-theme-accordion">
{starterSuggestions.map((item) => {
const theme = starterThemes.find((candidate) => candidate.id === item.theme);
return (
<button
className="starter-theme-card"
key={`${item.theme}-${item.text}`}
type="button"
aria-label={`${theme?.label || "开始"}${item.text}`}
disabled={productEntrypointsDisabled}
onClick={() => void startSuggestedConsultation(item.text, item.theme, "guided_topic")}
>
<span className="starter-content">
<b>{theme?.label || "开始"}</b>
<span>{item.text}</span>
</span>
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
</button>
);
})}
</div>
</section>
{onboardingError && <p className="starter-note"></p>}
</div>
);