fix(home,chart,ephemeris): drop the unfinished-rectification hint, full chart-type captions, orbit instead of 还没拿到, constellation icon (BUG-1111..1114)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-30 01:11:47 +08:00
co-authored by Claude Opus 5.5
parent 82db6373d6
commit 921992a492
14 changed files with 171 additions and 38 deletions
@@ -1,7 +1,6 @@
"use client";
import dynamic from "next/dynamic";
import { Sparkles } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
@@ -28,10 +27,28 @@ export function BirthSkyEntry({ cover }: { cover: BirthSkyCover | null }) {
return (
<>
<Button type="button" variant="secondary" size="sm" className="birth-sky-entry" onClick={() => setWanted(true)}>
<Sparkles aria-hidden="true" />
<ConstellationIcon />
{BIRTH_SKY_ENTRY_LABEL}
</Button>
{open ? <BirthSkyReplay cover={cover} onClose={() => setWanted(false)} /> : null}
</>
);
}
/**
* A small constellation: four stars joined by thin lines (2026-09-30, product:
* the sparkle icon is on every AI product). It says what the button opens —
* the stars of that moment drawing together — and is drawn here, not taken
* from an icon set.
*/
function ConstellationIcon() {
return (
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M2.5 11.5 L6 5 L10.5 7.5 L13.5 2.5" fill="none" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" opacity=".55" />
<circle cx="2.5" cy="11.5" r="1.3" fill="currentColor" />
<circle cx="6" cy="5" r="1.6" fill="currentColor" />
<circle cx="10.5" cy="7.5" r="1.2" fill="currentColor" />
<circle cx="13.5" cy="2.5" r="1.4" fill="currentColor" />
</svg>
);
}
@@ -1,4 +1,5 @@
import { chartLayerFailureCopy } from "@/lib/chart-view-failure";
import { OrbitWaiting } from "@/components/orbit-waiting";
import type { ChartViewFailureStatus, ChartViewOk } from "@/lib/chart-view-contract";
import { CHARA_ZERO_YEAR_LABEL, CHARA_ZERO_YEAR_NOTE, CHART_VIEW_COPY } from "@/lib/chart-view-labels";
@@ -154,7 +155,9 @@ export function ChartDashaTab({
<Track {...view.dasha.vimshottari} asOf={asOf} />
{charaPending || charaFailure ? (
<section className="chart-page-dasha-track">
<p className="chart-page-note">{charaPending ? CHART_VIEW_COPY.waitingLayer : chartLayerFailureCopy(charaFailure!)}</p>
{charaPending
? <OrbitWaiting label={CHART_VIEW_COPY.waitingLayer} />
: <p className="chart-page-note">{chartLayerFailureCopy(charaFailure!)}</p>}
</section>
) : (
<Track {...view.dasha.chara} asOf={asOf} />
@@ -1,5 +1,6 @@
import { EMPTY_NORTH_INDIAN_CHART, VedicChartSvg } from "@/components/personal-report/vedic-chart-svg";
import { EMPTY_WESTERN_WHEEL, WesternWheelSvg } from "./western-wheel-svg";
import { OrbitWaiting } from "@/components/orbit-waiting";
/** Only chart positions opt in; the shared page shell remains static. */
export function ChartPageSkeleton({ copy, western = false }: { copy: string; western?: boolean }) {
@@ -10,9 +11,10 @@ export function ChartPageSkeleton({ copy, western = false }: { copy: string; wes
{western ? <WesternWheelSvg western={EMPTY_WESTERN_WHEEL} skeleton />
: <VedicChartSvg chart={EMPTY_NORTH_INDIAN_CHART} ariaLabel={copy} skeleton />}
</div>
{/* Over the empty drawing, not under it: the finished chart has no line
below it any more (BUG-1101), so a line here would make it jump. */}
<p className="chart-page-note chart-page-skeleton-copy">{copy}</p>
{/* Over the empty drawing, not under it, so the waiting stage and the
finished one are the same height (BUG-1101). An orbit, not a sentence
(2026-09-30); the sentence is for screen readers. */}
<OrbitWaiting className="chart-page-skeleton-copy" label={copy} />
</div>
</div>
);
+17
View File
@@ -0,0 +1,17 @@
/**
* The one short-wait animation for secondary pages (2026-09-30, product: while
* a chart or a day's ephemeris loads, show motion instead of 「……还没拿到。」).
* It is the home loading screen's orbit ring at a smaller size, so the app has
* one waiting motif. The sentence stays for screen readers only.
*/
export function OrbitWaiting({ label, className = "" }: Readonly<{ label: string; className?: string }>) {
return (
<div className={`orbit-waiting${className ? ` ${className}` : ""}`} role="status">
<span className="app-loading-symbol" aria-hidden="true">
<span className="app-loading-orbit" />
<span className="app-loading-mark" />
</span>
<span className="sr-only">{label}</span>
</div>
);
}
@@ -3,6 +3,7 @@
import type { ReactNode } from "react";
import { CurrentSubjectSwitcher } from "@/components/current-subject-switcher";
import { OrbitWaiting } from "@/components/orbit-waiting";
import { SecondaryHeader } from "@/components/secondary-header";
export type SecondaryPageShellProps = {
@@ -40,7 +41,7 @@ export function SecondaryPageShell({
actions={actions}
/>
<div className="secondary-page">
{waiting ? <p className="secondary-page-waiting">{waiting}</p> : children}
{waiting ? <OrbitWaiting className="secondary-page-waiting" label={waiting} /> : children}
</div>
</>
);
+6 -11
View File
@@ -45,16 +45,11 @@ export function StarterGreeting({ starterGreeting, dailyTrend = null, dailyTrend
}
/* 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 {
it: a non-self subject cannot start a rectification. A resumable case says
nothing here any more (2026-09-30, product: 「这个提示可以去掉」); the case
is still in the history list. */
export function rectificationEntryHint(rectificationForSelf: boolean): string | null {
if (!rectificationForSelf) return "生时校正暂时只支持本人。";
if (summary?.hasResumableCase) return "上次那次校正还没完成,可以在历史对话里接着做。";
return null;
}
@@ -67,6 +62,7 @@ export type StarterEntriesProps = {
readonly rectificationLoading: boolean;
readonly rectificationMutationPending: boolean;
readonly openRectificationFromHomepage: () => void;
/** Still passed by the page; the hint no longer reads it (2026-09-30). */
readonly rectificationEntrySummary: RectificationEntrySummary | null;
readonly rectificationError: string;
readonly rectificationSurfaceOpen: boolean;
@@ -82,7 +78,6 @@ export function StarterEntries({
rectificationLoading,
rectificationMutationPending,
openRectificationFromHomepage,
rectificationEntrySummary,
rectificationError,
rectificationSurfaceOpen,
rectificationErrorMessage,
@@ -90,7 +85,7 @@ export function StarterEntries({
const subjectId = useCurrentSubject();
const rectificationForSelf = subjectId === "self";
const dailyLabel = natalMinuteAvailable ? "今日星语" : "每日运势";
const rectificationHint = rectificationEntryHint(rectificationForSelf, rectificationEntrySummary);
const rectificationHint = rectificationEntryHint(rectificationForSelf);
return (
<div className="starter-entries">