From 9958e00abc7d5248a68ea364b5e33b9823ecbe66 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Fri, 21 Aug 2026 15:29:58 +0800 Subject: [PATCH] fix(web): keep unrectified birth data usable without inventing a minute Rectification stays optional. Reported minutes can consult and generate reports; date-plus-period uses a declared window instead of a midpoint or 00:00. Updates BUG-341. Co-authored-by: Cursor --- docs/BUG_HISTORY.md | 16 ++ frontend/src/app/api/consult/route.ts | 175 ++++++++++++-- frontend/src/app/api/reports/route.ts | 2 +- frontend/src/app/page.tsx | 75 ++++-- .../generate-personal-report-button.tsx | 2 +- .../personal-report-center.tsx | 2 +- .../lib/birth-time-consultation-consent.ts | 17 +- frontend/src/lib/birth-time-journey.ts | 10 +- .../src/lib/consultation-birth-time-mode.ts | 19 +- frontend/src/lib/consultation-entrypoint.ts | 72 ++++-- frontend/src/lib/consultation-plan.ts | 11 +- .../src/lib/consultation-route-service.ts | 187 ++++++++++++++- frontend/src/lib/declared-birth-window.ts | 128 +++++++++++ frontend/src/lib/declared-window-chart.ts | 85 +++++++ .../src/lib/personal-report-route-core.ts | 40 +++- frontend/src/lib/personal-report-worker.ts | 21 +- .../rectification-agentic/v9/case-service.ts | 26 +-- frontend/src/lib/timing-output-guard.ts | 2 +- frontend/src/mastra/consultation-tools.ts | 138 ++++++++++- frontend/src/mastra/index.ts | 36 ++- .../birth-time-consultation-consent.test.ts | 10 +- .../consultation-birth-time-mode.test.ts | 28 ++- .../tests/consultation-entrypoint.test.ts | 58 ++++- frontend/tests/consultation-plan.test.ts | 5 + .../tests/consultation-route-service.test.ts | 11 +- frontend/tests/daily-starlanguage.test.ts | 4 +- frontend/tests/declared-birth-window.test.ts | 36 +++ frontend/tests/personal-report-api.test.ts | 35 ++- frontend/tests/personal-report-entry.test.ts | 9 +- frontend/tests/starter-questions.test.ts | 9 +- scripts/domain_calculation_service.py | 217 ++++++++++++++++++ scripts/jyotish_api_server.py | 31 +++ scripts/run_quality_gate.py | 1 + tests/test_declared_window_chart.py | 92 ++++++++ 34 files changed, 1441 insertions(+), 169 deletions(-) create mode 100644 frontend/src/lib/declared-birth-window.ts create mode 100644 frontend/src/lib/declared-window-chart.ts create mode 100644 frontend/tests/declared-birth-window.test.ts create mode 100644 tests/test_declared_window_chart.py diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index b2e86186..65f31479 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -5146,3 +5146,19 @@ - 相关记录:BUG-039、BUG-067 - 复发自:BUG-039(当时为了 attempt 隔离把增量攒到结算后,V9 流式合同被一起推迟) - 修复版本:994f0583 + +## BUG-341 | 无准确出生分钟时普通咨询被整题拒绝并叉到生时校正 + +- 状态:resolved(本地修复,待提交与发布) +- 首次发现:2026-08-21 +- 最近更新:2026-08-21 +- 影响面:无具体出生分钟用户的普通 session、`POST /api/consult` 一般咨询与声明窗口咨询、首页「每日运势」与主题卡、General Agent / Window Agent、个人报告入口、无分钟输出 guard +- 用户现象:初始化只填了日期/地点或大致时段、没有准确到分钟的出生时间后,在普通咨询里问「今天的运势」会被回复「当前一般咨询模式不能生成个人星盘结论」,并要求在一般知识和生时校正里二选一。用户再点「出生时间校正」仍留在普通 session,模型把服务端当前时间误认为用户提供了出生时间,然后改口给公共日盘。时段用户也无法进入事业/婚恋等主题咨询。未校正但已填报到分钟的用户无法生成个人报告。 +- 触发条件:`birth_time_source` 为 `period_only` / `unknown` 等无具体分钟状态;或已有 `reported_birth_time` 但状态仍是 `reported`/`candidate`;从首页「每日运势」或普通 session 发出今日运势或主题问题;或在同一普通 session 里发送「出生时间校正 / 生时校正」类标签。 +- 根因:产品把生时校正当成功能门,而不是可选增强。无分钟用户被压进 `general_no_birth_time`,General Agent 对任何本命预测整题拒绝并给出「百科 / 生时校正」二选一;首页无分钟日运还把 `daily_starlanguage` 入口丢掉。报告入口只认 `accepted`/`confirmed`,把未校正填报分钟也拦掉。这是 BUG-202 的回归叠加产品能力分层缺失。 +- 修复:校正改为可选。未校正填报分钟走 `unverified_birth_time`,可用咨询、今日星语和完整报告(标明未校正)。只有日期+时段或未知时刻走 `declared_birth_window`:约 4 个探针比较稳定层/变动层,禁止把中点、`00:00`、中午或探针写成出生分钟;主题咨询用窗口工具,每日运势仍走公共 Panchanga。无分钟百科咨询保留给真正没有日期/地点的情况。报告使用 `reported_birth_time` 且不把 reported 映射成 accepted。普通 session 的生时校正标签打开校正会话。 +- 验证:`frontend/tests/consultation-entrypoint.test.ts`、`frontend/tests/consultation-birth-time-mode.test.ts`、`frontend/tests/consultation-route-service.test.ts`、`frontend/tests/declared-birth-window.test.ts`、`frontend/tests/starter-questions.test.ts`、`frontend/tests/personal-report-api.test.ts`、`frontend/tests/timing-output-guard.test.ts`、`tests/test_declared_window_chart.py`。 +- 防复发:`period_only` / `unknown` 不得再进无盘死胡同。窗口探针不得用时段中点、`00:00` 或任意单一分钟冒充出生时间。窗口请求不得携带 `hour`/`minute`。未校正填报分钟可出报告,窗口用户仍不得出完整本命报告。无分钟「每日运势」不得把 `daily_starlanguage` 丢掉。普通 session 不得把生时校正标签当一般咨询原文。General Agent 不得再写「exactly two safe next steps」。 +- 相关记录:BUG-009、BUG-073、BUG-202、BUG-273 +- 复发自:BUG-202(服务端公共日盘已修好,首页无分钟入口后来又把 entrypoint 丢掉,普通 session 仍走整题拒绝;校正还被当成功能门) +- 修复版本:待提交 diff --git a/frontend/src/app/api/consult/route.ts b/frontend/src/app/api/consult/route.ts index be34fe3e..4673ee22 100644 --- a/frontend/src/app/api/consult/route.ts +++ b/frontend/src/app/api/consult/route.ts @@ -5,6 +5,7 @@ import { getGeneralJyotishAgent, getJyotishAgent, getLegacyJyotishAgent, + getWindowJyotishAgent, runConsultationWorkflow, } from "@/mastra"; import { blocksPromptExtraction } from "@/lib/consult-safety"; @@ -25,6 +26,7 @@ import { import { consultationEntrypointSchema, resolveConsultationQuestion, + shouldLoadGeneralDailyPanchanga, } from "@/lib/consultation-entrypoint"; import { CreditRpcError } from "@/lib/consultation-billing"; import { reserveConsultationModel } from "@/lib/consultation-model-selection"; @@ -39,6 +41,7 @@ import { AGENT_TIMEOUT_MS, consultationGenerationSettings, createConsultationAgentContext, + createWindowConsultationAgentContext, consultationModelStepTelemetry, consultationStepBudgetReceipt, createConsultationRuntimeHooks, @@ -50,6 +53,7 @@ import { consultationBirthTimeModeSchema, createBirthTimeModeOutputGuard, shouldRunBirthChartWorkflow, + shouldRunDeclaredWindowWorkflow, type ConsultationBirthTimeMode, } from "@/lib/consultation-birth-time-mode"; import { @@ -89,7 +93,7 @@ const chatRequestMetadataSchema = z.object({ const chartChatRequestSchema = consultationInputSchema.extend({ ...chatRequestMetadataSchema.shape, - consultationMode: consultationBirthTimeModeSchema.exclude(["general_no_birth_time"]) + consultationMode: consultationBirthTimeModeSchema.exclude(["general_no_birth_time", "declared_birth_window"]) .optional() .default("verified_chart"), entrypoint: consultationEntrypointSchema.optional(), @@ -103,7 +107,19 @@ const generalChatRequestSchema = z.object({ entrypoint: z.literal("daily_starlanguage").optional(), }).strict(); -const chatRequestSchema = z.union([generalChatRequestSchema, chartChatRequestSchema]); +const windowChatRequestSchema = z.object({ + ...chatRequestMetadataSchema.shape, + consultationMode: z.literal("declared_birth_window"), + question: z.string().trim().min(1).max(500), + theme: consultationDomainSchema, + entrypoint: z.literal("daily_starlanguage").optional(), +}).strict(); + +const chatRequestSchema = z.union([ + generalChatRequestSchema, + windowChatRequestSchema, + chartChatRequestSchema, +]); const consultationReservationSchema = z.object({ success: z.boolean(), @@ -160,6 +176,24 @@ function generalDailyContextPrompt(context: GeneralDailyPanchangaContext | null) ].join("\n"); } +function generalNoMinuteInstruction(hasPublicDaily: boolean) { + return hasPublicDaily + ? "当前是无出生分钟的公共今日趋势咨询。可依据服务器提供的公共 Panchanga 摘要回答,但不得计算或推断个人星盘;不得补 00:00、时段中点或任何候选分钟。" + : "当前没有具体出生分钟。不得计算或推断个人星盘,也不得补 00:00、时段中点或任何候选分钟。请直接回答用户的问题:能答的部分照实说,需要出生分钟的部分明确标出限制。不要把整轮对话改成「一般知识或生时校正」二选一,也不要把服务端当前时间当成用户提供的出生时间。"; +} + +function declaredWindowInstruction() { + return "当前是声明出生窗口咨询,没有单一出生分钟。如需个人结构结论,必须调用 run-jyotish-window-consultation。探针时刻不是出生时间,不得把 00:00、时段中点、中午或任一探针写成出生分钟。只能根据稳定层作方向性回答;上升或宫位若在窗口内变化,列出可能星座,不得写成「你的上升是 X」。精确应期一律不可用。"; +} + +function usesPublicDailyGeneralAgent( + consultationMode: ConsultationBirthTimeMode, + generalDailyContext: GeneralDailyPanchangaContext | null, +) { + return consultationMode === "general_no_birth_time" + || (consultationMode === "declared_birth_window" && Boolean(generalDailyContext)); +} + function chinaCalendarDate(now: Date) { return new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10); } @@ -307,7 +341,7 @@ export async function POST(request: Request) { async loadProfile(profileUserId) { const { data, error } = await supabase .from("profiles") - .select("name,birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source") + .select("name,birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source") .eq("id", profileUserId) .single(); if (error || !data) throw new ConsultationProfileTruthError("profile_unavailable"); @@ -638,11 +672,11 @@ export async function POST(request: Request) { content: [ currentTimeContext(requestTime), name ? `用户称呼:${name}` : "", - consultationMode === "general_no_birth_time" - ? generalDailyContext - ? "当前是无出生分钟的公共今日趋势咨询。可依据服务器提供的公共 Panchanga 摘要回答,但不得计算或推断个人星盘;不得补 00:00、时段中点或任何候选分钟。" - : "当前是用户明确选择的无出生分钟一般咨询。不得计算或推断个人星盘;不得补 00:00、时段中点或任何候选分钟。" - : "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。", + consultationMode === "general_no_birth_time" || (consultationMode === "declared_birth_window" && generalDailyContext) + ? generalNoMinuteInstruction(Boolean(generalDailyContext)) + : consultationMode === "declared_birth_window" + ? declaredWindowInstruction() + : "先加载 Jyotish Skill;如需新的个人星盘结论,必须调用服务器绑定的排盘工具。事业/财富/婚恋/家庭按 skill Level 2 模板写:原始结构、六步宫位、Yoga 表、时机、综合、文末技法审计表,然后才是现代生活措辞。不要复述内部 JSON 字段。", generalDailyContextPrompt(generalDailyContext), resolvedQuestion.modelQuestion, ].filter(Boolean).join("\n"), @@ -656,16 +690,23 @@ export async function POST(request: Request) { hooks, ...consultationGenerationSettings(selectedModel.model), }; - const workflowReceipt: WorkflowReceipt = consultationMode === "general_no_birth_time" + const workflowReceipt: WorkflowReceipt = usesPublicDailyGeneralAgent(consultationMode, generalDailyContext) ? { route: generalDailyContext ? "general-daily-panchanga" : "general-no-birth-time", status: "ready", preciseTiming: "blocked", missingLayers: ["birth-minute"], } - : { route: "pending", status: "blocked", preciseTiming: "blocked", missingLayers: [] }; + : consultationMode === "declared_birth_window" + ? { + route: "declared-birth-window", + status: "blocked", + preciseTiming: "blocked", + missingLayers: ["birth-minute"], + } + : { route: "pending", status: "blocked", preciseTiming: "blocked", missingLayers: [] }; - if (consultationMode === "general_no_birth_time") { + if (usesPublicDailyGeneralAgent(consultationMode, generalDailyContext)) { state.workflowReceipt = workflowReceipt; const agent = getGeneralJyotishAgent(selectedModel); const result = await agent.stream(baseMessages, streamOptions); @@ -705,7 +746,10 @@ export async function POST(request: Request) { requireTool: false, retryForAnswer, continueAfterDisconnect: true, - transformText: createBirthTimeModeOutputGuard(consultationMode, false), + transformText: createBirthTimeModeOutputGuard( + generalDailyContext ? "general_no_birth_time" : consultationMode, + false, + ), toolStatus: () => "ready", receipt: executionReceipt, headers: { "x-jyotish-birth-time-mode": consultationMode }, @@ -726,12 +770,97 @@ export async function POST(request: Request) { }); } + if (shouldRunDeclaredWindowWorkflow(consultationMode)) { + if (!prepared.declaredWindow) throw new Error("declared_window_truth_missing"); + const agentContext = createWindowConsultationAgentContext({ + userId, + sessionId, + requestId, + consultationMode: "declared_birth_window", + plan: prepared.preReserveResult, + theme: consultationTheme, + declaredWindow: prepared.declaredWindow, + abortSignal: agentAbortSignal, + state, + }); + const agent = getWindowJyotishAgent(selectedModel, agentContext); + const result = await agent.stream(baseMessages, streamOptions); + usages.push(result.totalUsage); + const retry = async () => { + const retried = await agent.stream([ + ...baseMessages, + { + role: "user" as const, + content: "运行合同不完整:本次尚未取得声明窗口计算结果。请调用 run-jyotish-window-consultation 完成计算,再据此回答;不要在工具参数中添加出生分钟。", + }, + ], streamOptions); + usages.push(retried.totalUsage); + return retried.fullStream; + }; + const retryForAnswer = async () => { + const retried = await agent.stream([ + ...baseMessages, + { + role: "user" as const, + content: "服务器窗口计算已经完成,但上一轮没有输出任何回答文本。请重新取回本次计算结果,然后直接给出回答;不要只描述过程或工具调用。", + }, + ], streamOptions); + usages.push(retried.totalUsage); + return retried.fullStream; + }; + const executionReceipt = (): AgentExecutionReceipt => ({ + runId: requestId, + runtime: "mastra-agentic", + skill: { + name: "jyotish-vedic-astrology", + loaded: state.jyotishSkillBound, + referenceReads: state.skillReferenceReadCount, + methodologySections: state.methodologySectionCount, + }, + steps: publicConsultationRuntimeSteps(state), + stepBudget: consultationStepBudgetReceipt(state), + workflow: state.workflowReceipt ?? workflowReceipt, + techniqueTruth: state.techniqueTruth ?? "declared-window", + ...(state.techniqueAuditTable?.length + ? { techniqueAuditTable: state.techniqueAuditTable } + : {}), + }); + return streamAgentResponse({ + runId: requestId, + requestId, + state, + stream: result.fullStream, + requireTool: true, + retry, + retryForAnswer, + continueAfterDisconnect: true, + transformText: createBirthTimeModeOutputGuard(consultationMode, false), + toolStatus: () => workflowStatus(state.workflowReceipt?.status), + receipt: executionReceipt, + headers: { "x-jyotish-birth-time-mode": consultationMode }, + onFirstActivity: markFirstActivity, + onFirstOutput: markFirstText, + onComplete: (output, agentExecutionReceipt) => settleRun(() => completeResponse( + output, + mergeUsage(usages), + state.techniqueTruth ?? "declared-window", + state.workflowReceipt ?? workflowReceipt, + agentExecutionReceipt, + ), undefined), + onError: (error) => settleRun( + cancel, + toAgentObservabilityErrorCode(error), + ), + onCancel: () => settleRun(cancel, "cancelled"), + }); + } + if (!prepared.serverChart) throw new Error("server_chart_truth_missing"); const agentContext = createConsultationAgentContext({ userId, sessionId, requestId, - consultationMode, + consultationMode: consultationMode === "verified_chart" ? "verified_chart" : "unverified_birth_time", plan: prepared.preReserveResult, theme: consultationTheme, serverChart: prepared.serverChart, @@ -818,16 +947,19 @@ export async function POST(request: Request) { try { const { history } = parsed.data; - const name = prepared.serverChart?.name ?? parsed.data.name; + const name = prepared.serverChart?.name ?? prepared.declaredWindow?.name ?? parsed.data.name; const consultationMode: ConsultationBirthTimeMode = prepared.consultationMode; - const generalDailyContext = consultationMode === "general_no_birth_time" - && parsed.data.entrypoint === "daily_starlanguage" + const generalDailyContext = shouldLoadGeneralDailyPanchanga({ + consultationMode, + entrypoint: parsed.data.entrypoint, + visibleQuestion: parsed.data.question, + }) ? await loadGeneralDailyPanchangaContext({ date: currentDate, reference: prepared.generalDailyReference, }) : null; - if (shouldUseAgenticRuntime(user)) { + if (shouldUseAgenticRuntime(user) || shouldRunDeclaredWindowWorkflow(consultationMode)) { return await runAgenticConsultation(consultationMode, history, name, generalDailyContext); } if (!shouldRunBirthChartWorkflow(consultationMode)) { @@ -837,9 +969,7 @@ export async function POST(request: Request) { content: [ currentTimeContext(requestTime), name ? `用户称呼:${name}` : "", - generalDailyContext - ? "当前是无出生分钟的公共今日趋势咨询。可依据服务器提供的公共 Panchanga 摘要回答,但不得计算或推断个人星盘;不得补 00:00、时段中点或任何候选分钟。" - : "当前是用户明确选择的无出生分钟一般咨询。不得计算或推断个人星盘;不得补 00:00、时段中点或任何候选分钟。", + generalNoMinuteInstruction(Boolean(generalDailyContext)), generalDailyContextPrompt(generalDailyContext), resolvedQuestion.modelQuestion, ].filter(Boolean).join("\n"), @@ -862,7 +992,10 @@ export async function POST(request: Request) { : cancel, ); return streamTextResponse(result.textStream, { - transformText: createBirthTimeModeOutputGuard(consultationMode, false), + transformText: createBirthTimeModeOutputGuard( + generalDailyContext ? "general_no_birth_time" : consultationMode, + false, + ), mode: "mastra", requestId, continueAfterDisconnect: true, diff --git a/frontend/src/app/api/reports/route.ts b/frontend/src/app/api/reports/route.ts index f0e91e26..50b4ea47 100644 --- a/frontend/src/app/api/reports/route.ts +++ b/frontend/src/app/api/reports/route.ts @@ -124,7 +124,7 @@ export async function POST(request: Request) { if (userId) { const result = await supabase .from("profiles") - .select("name,birth_date,active_birth_time,birth_time_status,latitude,longitude,timezone_offset,birth_place_label") + .select("name,birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_status,latitude,longitude,timezone_offset,birth_place_label") .eq("id", userId) .maybeSingle(); profile = result.data ?? null; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index fb6cfaf0..3b402d96 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -46,7 +46,12 @@ import { type BeamAvatar, type BeamAvatarPatch, } from "@/lib/beam-avatar"; -import type { ConsultationEntrypoint } from "@/lib/consultation-entrypoint"; +import { + GENERAL_NO_MINUTE_DAILY_FORTUNE_QUESTION, + isGeneralDailyFortuneQuestion, + isRectificationHandoffQuestion, + type ConsultationEntrypoint, +} from "@/lib/consultation-entrypoint"; import { applyBirthTimeDraftPatch, applyPersistedBirthTime, @@ -1287,12 +1292,15 @@ export default function Home() { const profileComplete = isProfileComplete(profile); const birthTimeRoute = resolveBirthTimeConsultationRoute(profile, birthTimeConsultationConsent, activeSessionId); - const personalChartAvailable = birthTimeRoute.kind === "consult" && birthTimeRoute.mode !== "general_no_birth_time"; + const natalMinuteAvailable = birthTimeRoute.kind === "consult" + && (birthTimeRoute.mode === "verified_chart" || birthTimeRoute.mode === "unverified_birth_time"); + const personalConsultationAvailable = birthTimeRoute.kind === "consult" + && birthTimeRoute.mode !== "general_no_birth_time"; const dailyStarlanguageFingerprint = dailyStarlanguageProfileKey(profile); - const starterThemes = personalChartAvailable ? themes : generalGuidedJyotishTopics; - const dailyStarlanguageQuestion = !personalChartAvailable - ? "请帮我看一下今天的运势,重点告诉我适合推进什么、需要注意什么。" + const starterThemes = personalConsultationAvailable ? themes : generalGuidedJyotishTopics; + const dailyStarlanguageQuestion = !natalMinuteAvailable + ? GENERAL_NO_MINUTE_DAILY_FORTUNE_QUESTION : dailyStarlanguage.kind === "unavailable" ? "从今日问起" : "深入看今日"; @@ -1302,7 +1310,7 @@ export default function Home() { ? "正在写下今天的星语。" : "今天的星语还没写出来。"; const dailyStarlanguageAction = dailyStarlanguage.kind === "ready" ? dailyStarlanguage.card.action : ""; - const dailyStarlanguageBusy = personalChartAvailable && dailyStarlanguage.kind === "pending"; + const dailyStarlanguageBusy = natalMinuteAvailable && dailyStarlanguage.kind === "pending"; const onboardingPending = profileComplete && !onboarding && !onboardingError; const currentOnboardingMessage = onboardingJustCompleted ? startGreeting || completedOnboardingMessage(profileDraft.name.trim()) @@ -1316,7 +1324,7 @@ export default function Home() { const shouldStreamOnboarding = !profileComplete; const presetMessageFinished = !shouldStreamOnboarding || presetMessageLength >= currentOnboardingMessage.length; const onboardingCardReady = presetMessageFinished || birthTimeAssessmentPhase !== null; - const starterSuggestions = starterThemes.map((theme) => personalChartAvailable + const starterSuggestions = starterThemes.map((theme) => personalConsultationAvailable ? onboarding?.suggestions.find((item) => item.theme === theme.id) ?? { theme: theme.id, text: theme.prompt } : { theme: theme.id, text: theme.prompt }); const starterHomeVisible = profileComplete @@ -1814,7 +1822,7 @@ export default function Home() { }, [accountId, hydrated, onboardingFingerprint, profile.name, profileComplete]); useEffect(() => { - if (!hydrated || !accountId || !profileComplete || !personalChartAvailable) return; + if (!hydrated || !accountId || !profileComplete || !natalMinuteAvailable) return; const today = calendarDateInTimeZone(new Date(), profile.timezoneId); const fingerprint = dailyStarlanguageProfileKey(profile); const stored = readStoredDailyStarlanguage(accountId); @@ -1852,7 +1860,7 @@ export default function Home() { controller.abort(); if (retryTimer !== undefined) clearTimeout(retryTimer); }; - }, [accountId, dailyStarlanguageFingerprint, hydrated, personalChartAvailable, profileComplete]); + }, [accountId, dailyStarlanguageFingerprint, hydrated, natalMinuteAvailable, profileComplete]); useEffect(() => { if (starterHomeVisible) { @@ -2485,7 +2493,7 @@ export default function Home() { chooseSuggestedQuestion( dailyStarlanguageQuestion, "timing", - personalChartAvailable ? "daily_starlanguage" : null, + "daily_starlanguage", ); } @@ -2944,6 +2952,12 @@ export default function Home() { ): Promise { const originalQuestion = text; const question = text.trim(); + const consultEntrypoint = entrypoint + ?? (isRectificationHandoffQuestion(question) + ? "birth_time_rectification" as const + : isGeneralDailyFortuneQuestion(question) + ? "daily_starlanguage" as const + : null); const resumeRequestId = options.resumeRequestId; const resuming = Boolean(resumeRequestId); const liveSession = targetSessionId @@ -2967,8 +2981,19 @@ export default function Home() { return false; } - if (entrypoint === "birth_time_rectification") { - await openRectificationFromHomepage(); + if (consultEntrypoint === "birth_time_rectification") { + const previousUserQuestion = [...currentSession.messages] + .reverse() + .find((message) => message.role === "user") + ?.text + .trim() ?? null; + const pendingQuestion = previousUserQuestion && !isRectificationHandoffQuestion(previousUserQuestion) + ? previousUserQuestion + : null; + await openRectificationFromHomepage(pendingQuestion); + setDraft(""); + setDraftTheme(null); + setDraftEntrypoint(null); return false; } @@ -3036,7 +3061,7 @@ export default function Home() { requestId, sessionId, question: originalQuestion, - entrypoint, + entrypoint: consultEntrypoint, theme, previousSession: rollbackSession, optimisticSession: userSession, @@ -3052,7 +3077,7 @@ export default function Home() { requestId, question: originalQuestion, theme, - entrypoint, + entrypoint: consultEntrypoint, })); } catch { // Private-mode storage must not block send. @@ -3117,7 +3142,7 @@ export default function Home() { if (!options.restoreOnFailure && activeSessionIdRef.current === sessionId) { setDraft(originalQuestion); setDraftTheme(theme); - setDraftEntrypoint(entrypoint); + setDraftEntrypoint(consultEntrypoint); } setRequestError({ sessionId, @@ -3135,7 +3160,7 @@ export default function Home() { pendingConsultation.current = { ...pendingConsultation.current, question: originalQuestion, - entrypoint, + entrypoint: consultEntrypoint, theme, optimisticSession: userSession, phase: "streaming", @@ -3154,8 +3179,8 @@ export default function Home() { modelId: currentSession.modelId, name: profile.name, consultationMode: consultationRoute.mode, - entrypoint: entrypoint ?? undefined, - ...(consultationRoute.mode === "general_no_birth_time" ? {} : { + entrypoint: consultEntrypoint ?? undefined, + ...(consultationRoute.mode === "general_no_birth_time" || consultationRoute.mode === "declared_birth_window" ? {} : { year, month, day, @@ -3700,20 +3725,20 @@ export default function Home() {