fix(home): serve today's starlanguage from daily guidance
Independent Staging Quality Gate / validate (push) Failing after 8m9s
Independent Staging Quality Gate / publish (push) Has been skipped

The homepage was waiting on a full chart plus four extra engines, so the card timed out and showed the failure copy on every visit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-19 15:35:38 +08:00
co-authored by Cursor
parent e635c40224
commit 8db716ca36
10 changed files with 234 additions and 91 deletions
@@ -4,8 +4,8 @@ import {
composeDailyStarlanguageCard,
dailyStarlanguageCacheKey,
dailyStarlanguageCardSchema,
dailyStarlanguageEvidence,
dailyStarlanguagePrompt,
evidenceFromDailyGuidance,
parseDailyStarlanguageText,
type DailyStarlanguageCard,
type DailyStarlanguageEvidence,
@@ -19,7 +19,7 @@ import { createServerSupabaseClient } from "@/lib/supabase/server";
import { getDailyStarlanguageAgent } from "@/mastra";
export const runtime = "nodejs";
export const maxDuration = 30;
export const maxDuration = 20;
type Profile = GlobalBirthProfile & { birthTimeStatus?: string; timezoneId?: string };
type BirthPayload = NonNullable<Awaited<ReturnType<typeof dailyProfilePayload>>>;
@@ -35,7 +35,7 @@ type Generated =
| { readonly kind: "failed"; readonly reason: "chart_unavailable" };
const jyotishApiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
const engineTimeoutMs = 20_000;
const engineTimeoutMs = 8_000;
const agentTimeoutMs = 45_000;
const cacheLimit = 500;
@@ -89,37 +89,17 @@ async function fetchEngine(path: string, body: Record<string, unknown>) {
return await response.json() as Record<string, unknown>;
}
function chartPoints(chart: Record<string, unknown>) {
const modules = chart.modules && typeof chart.modules === "object" ? chart.modules as Record<string, unknown> : {};
const chartModule = modules.chart && typeof modules.chart === "object" ? modules.chart as Record<string, unknown> : chart;
const planets = chartModule.planets && typeof chartModule.planets === "object" ? chartModule.planets : undefined;
const ascendant = chartModule.ascendant && typeof chartModule.ascendant === "object" ? chartModule.ascendant : undefined;
return planets && ascendant ? { planets, ascendant } : null;
}
async function collectLayers(payload: BirthPayload, today: string) {
const chart = await fetchEngine("/api/chart", payload).catch((error: unknown) => {
console.warn("daily_starlanguage_chart_unavailable", error);
async function loadDailyGuidance(payload: BirthPayload, today: string) {
const packet = await fetchEngine("/api/daily_guidance", {
...payload,
date: today,
reference_date: today,
}).catch((error: unknown) => {
console.warn("daily_starlanguage_guidance_unavailable", error);
return null;
});
const points = chart === null ? null : chartPoints(chart);
if (chart === null || !points) return null;
const tomorrow = new Date(`${today}T00:00:00.000Z`);
tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
const withPoints = { ...payload, planets: points.planets, ascendant: points.ascendant };
const [vimshottari, narayana, varga, transit] = await Promise.all([
fetchEngine("/api/dasha", { ...withPoints, dasha: "vimshottari", today }).catch(() => null),
fetchEngine("/api/dasha", { ...withPoints, dasha: "narayana", today }).catch(() => null),
fetchEngine("/api/varga_full", { ...withPoints, divisions: ["D9", "D10"] }).catch(() => null),
fetchEngine("/api/transit", {
natal_planets: points.planets,
ascendant: points.ascendant,
start: today,
end: tomorrow.toISOString().slice(0, 10),
planets_to_check: ["Saturn", "Jupiter", "Rahu", "Ketu", "Mars", "Sun"],
}).catch(() => null),
]);
return { chart, vimshottari, narayana, varga, transit };
if (!packet || packet.success !== true) return null;
return packet;
}
async function polishWithAgent(evidence: DailyStarlanguageEvidence): Promise<DailyStarlanguageCard | null> {
@@ -158,9 +138,10 @@ function scheduleAgentPolish(key: string, today: string, evidence: DailyStarlang
}
async function generateCard(payload: BirthPayload, profile: Profile, today: string): Promise<Generated> {
const layers = await collectLayers(payload, today);
if (!layers) return { kind: "failed", reason: "chart_unavailable" };
const evidence = dailyStarlanguageEvidence(layers, today, profile.birthTimeStatus === "confirmed");
const packet = await loadDailyGuidance(payload, today);
if (!packet) return { kind: "failed", reason: "chart_unavailable" };
const evidence = evidenceFromDailyGuidance(packet, today, profile.birthTimeStatus === "confirmed");
if (!evidence) return { kind: "failed", reason: "chart_unavailable" };
return {
kind: "card",
card: composeDailyStarlanguageCard(evidence),
+14
View File
@@ -1297,6 +1297,20 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
text-wrap: pretty;
}
.product-entrypoint-copy p[aria-busy="true"] {
color: var(--color-ink-tertiary);
}
@media (prefers-reduced-motion: no-preference) {
.product-entrypoint-copy p[aria-busy="true"] {
animation: daily-starlanguage-pending 1.6s ease-in-out infinite;
}
}
@keyframes daily-starlanguage-pending {
50% { opacity: .55; }
}
.product-entrypoint-footer {
display: flex;
align-items: flex-end;
+15 -8
View File
@@ -1274,12 +1274,18 @@ export default function Home() {
const dailyStarlanguageFingerprint = dailyStarlanguageProfileKey(profile);
const starterThemes = personalChartAvailable ? themes : generalGuidedJyotishTopics;
const dailyStarlanguageQuestion = !personalChartAvailable
? "请帮我看一下今天的运势,重点告诉我适合推进什么、需要注意什么。"
: dailyStarlanguage.kind === "unavailable"
? "从今日问起"
: "深入看今日";
const dailyStarlanguageTrend = dailyStarlanguage.kind === "ready"
? dailyStarlanguage.card.trend
: dailyStarlanguage.kind === "pending"
? "正在结合你的星盘写今天的星语。"
: "今天的星语没能生成。点这里从今日问起;不会用通用文案顶替。";
? "正在写下今天的星语。"
: "今天的星语还没写出来。";
const dailyStarlanguageAction = dailyStarlanguage.kind === "ready" ? dailyStarlanguage.card.action : "";
const dailyStarlanguageBusy = personalChartAvailable && dailyStarlanguage.kind === "pending";
const onboardingPending = profileComplete && !onboarding && !onboardingError;
const currentOnboardingMessage = onboardingJustCompleted
? startGreeting || completedOnboardingMessage(profileDraft.name.trim())
@@ -2448,9 +2454,7 @@ export default function Home() {
function draftDailyStarlanguageQuestion() {
chooseSuggestedQuestion(
personalChartAvailable
? "深入看今日"
: "请帮我看一下今天的运势,重点告诉我适合推进什么、需要注意什么。",
dailyStarlanguageQuestion,
"timing",
personalChartAvailable ? "daily_starlanguage" : null,
);
@@ -3550,17 +3554,20 @@ export default function Home() {
<button
className="product-entrypoint-hitarea"
type="button"
aria-label={personalChartAvailable ? "深入看今日" : "查看今日运势"}
aria-label={personalChartAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
disabled={productEntrypointsDisabled}
onClick={draftDailyStarlanguageQuestion}
/>
<div className="product-entrypoint-copy">
<h2 id="daily-starlanguage-title">{personalChartAvailable ? "今日星语" : "每日运势"}</h2>
<p aria-busy={personalChartAvailable && dailyStarlanguage.kind === "pending"}>{personalChartAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}</p>
<p
aria-busy={dailyStarlanguageBusy}
role={personalChartAvailable && dailyStarlanguage.kind !== "ready" ? "status" : undefined}
>{personalChartAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}</p>
</div>
<div className="product-entrypoint-footer">
<small>{personalChartAvailable ? dailyStarlanguageAction : "不支持的个人判断会明确说明,不会补造出生时间。"}</small>
<span className="product-entrypoint-action" aria-hidden="true">{personalChartAvailable ? "深入看今日" : "查看今日运势"} <ArrowUpRight className="starter-arrow" /></span>
<span className="product-entrypoint-action" aria-hidden="true">{personalChartAvailable ? dailyStarlanguageQuestion : "查看今日运势"} <ArrowUpRight className="starter-arrow" /></span>
</div>
</article>
<article className="birth-rectification-card product-entrypoint-card" aria-labelledby="birth-rectification-title">