fix(home): stop silent rectification open after login (BUG-599)
Independent Staging Quality Gate / validate (push) Successful in 13m10s
Independent Staging Quality Gate / publish (push) Has been cancelled

Bare `/` was treating the latest history session as active, so a returning account could auto-open a failed rectification Case and left-align the starter home.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 10:47:47 +08:00
co-authored by Cursor
parent a43a6db884
commit c9ec541164
8 changed files with 199 additions and 14 deletions
+3
View File
@@ -1903,11 +1903,13 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
/* Starter workbench */
.welcome {
width: min(1040px, 100%);
margin-inline: auto;
padding: var(--space-8) 0 var(--space-12);
}
.starter-list {
width: min(1040px, 100%);
margin-inline: auto;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--space-6);
@@ -2229,6 +2231,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
.composer-wrap-starter .composer,
.composer-wrap-starter .composer-footer {
width: min(1040px, 100%);
margin-inline: auto;
}
@media (min-width: 768px) and (max-width: 900px) {
+48 -10
View File
@@ -231,7 +231,10 @@ import {
bootstrapLoadingCopy,
bootstrapPrepareSettled,
bootstrapRevealDelayMs,
resolveStarterHomeLandingSessionId,
sessionIdsToPrefetch,
shouldAutoOpenRectificationSession,
starterHomeLandingNeedsConsultation,
type BootstrapPhase,
} from "@/lib/home-bootstrap";
@@ -520,16 +523,19 @@ export default function Home() {
return () => window.removeEventListener("popstate", onPopState);
}, [hydrated]);
// A rectification session selected at bootstrap (deep link, refresh, the
// most recent session) is opened and hydrated during the prepare phase, so
// the reveal shows the surface itself rather than a plain transcript that is
// swapped out a moment later. After the reveal the same effect serves
// history navigation.
// A rectification session named in `?c=` (refresh, deep link, login return)
// is opened during prepare so the reveal shows the surface. Bare `/` after
// login must not open the latest history item in the background.
useEffect(() => {
if ((!hydrated && bootstrapPhase === "account")
|| !account
|| !modelCatalog
|| activeSession?.sessionType !== "birth_time_rectification"
|| !activeSession
|| !shouldAutoOpenRectificationSession({
sessionType: activeSession.sessionType,
sessionId: activeSession.id,
search: window.location.search,
})
|| activeSession.id === rectificationSessionId
|| rectificationLoading
|| rectificationMutationPending
@@ -671,7 +677,11 @@ export default function Home() {
dailyStarlanguageApplicable: Boolean(accountId) && profileComplete && natalMinuteAvailable,
dailyStarlanguageSettled: dailyStarlanguage.kind !== "pending",
entrySummarySettled: rectificationEntrySummarySettled,
rectificationApplicable: activeSession?.sessionType === "birth_time_rectification",
rectificationApplicable: shouldAutoOpenRectificationSession({
sessionType: activeSession?.sessionType,
sessionId: activeSession?.id,
search: typeof window === "undefined" ? "" : window.location.search,
}),
rectificationSettled: activeSession?.id === rectificationSessionId
|| rectificationError !== ""
|| !profileComplete,
@@ -1013,7 +1023,35 @@ export default function Home() {
search: window.location.search,
storedReturnId: readLoginSessionReturn(),
});
const activeListed = nextSessions.find((session) => session.id === bootstrapSelection.sessionId)
let landingSessionId = resolveStarterHomeLandingSessionId(
nextSessions,
bootstrapSelection.sessionId,
bootstrapSelection.urlAction,
);
if (starterHomeLandingNeedsConsultation(nextSessions, landingSessionId, bootstrapSelection.urlAction)) {
if (controller.signal.aborted) return;
const homeSession = createSession(
nextModelCatalog?.defaultModelId ?? "",
"consultation",
chartSnapshotForSession("self", [], nextProfile),
);
if (nextModelCatalog) {
await writeChatSession(homeSession.id, {
title: homeSession.title,
theme: homeSession.theme,
model_id: homeSession.modelId,
messages: [],
session_type: homeSession.sessionType,
rectification_case_id: homeSession.rectificationCaseId,
chart_profile_id: homeSession.chartProfileId,
chart_profile_name: homeSession.chartProfileName,
chart_profile_role: homeSession.chartProfileRole,
}, "create");
}
nextSessions = [homeSession, ...nextSessions];
landingSessionId = homeSession.id;
}
const activeListed = nextSessions.find((session) => session.id === landingSessionId)
?? nextSessions[0];
if (activeListed && !activeListed.messagesHydrated && activeListed.sessionType === "consultation") {
try {
@@ -1035,7 +1073,7 @@ export default function Home() {
setStartGreeting(nextProfile.name.trim() ? createStartGreeting(nextProfile.name) : "");
setOnboardingStep(missingProfileStep(nextProfile) ?? "name");
setSessions(nextSessions);
setActiveSessionId(bootstrapSelection.sessionId);
setActiveSessionId(landingSessionId);
if (bootstrapSelection.clearStoredReturn) clearLoginSessionReturn();
if (bootstrapSelection.urlAction === "replace-clear") writeSessionUrl(null, "replace");
if (bootstrapSelection.urlAction === "replace-selected") writeSessionUrl(bootstrapSelection.sessionId, "replace");
@@ -1744,7 +1782,7 @@ export default function Home() {
</header>
{!rectificationSurfaceOpen && (
<div ref={conversation} className={`conversation${!activeSession?.messages.length && Boolean(activeSession?.messagesHydrated) && !onboardingFormActive ? " is-empty" : ""}${!profileComplete ? " is-onboarding" : ""}${onboardingFormActive ? " is-onboarding-form" : ""}`}>
<div ref={conversation} className={`conversation${!onboardingFormActive && !activeSession?.messages.length && (Boolean(activeSession?.messagesHydrated) || activeSession?.sessionType !== "consultation") ? " is-empty" : ""}${!profileComplete ? " is-onboarding" : ""}${onboardingFormActive ? " is-onboarding-form" : ""}`}>
{!activeSession?.messages.length ? (
<div className="welcome">
{!profileComplete ? (
+55 -1
View File
@@ -8,8 +8,62 @@
* settled, or when the prepare budget runs out never with a spinner inside.
*/
import { parseSessionUrlQuery } from "./chat-session-url.ts";
export type BootstrapPhase = "account" | "prepare";
export type LandingSession = Readonly<{
id: string;
sessionType: string;
messages: readonly unknown[];
}>;
export type LandingUrlAction = "keep" | "replace-selected" | "replace-clear" | "none";
/**
* A rectification Case is opened only when the address bar names that session
* (`?c=`), including refresh and a login return that restored the URL. Bare `/`
* after login must not open the latest history item in the background.
*/
export function shouldAutoOpenRectificationSession(input: {
readonly sessionType: string | undefined;
readonly sessionId: string | undefined;
readonly search: string;
}): boolean {
if (input.sessionType !== "birth_time_rectification" || !input.sessionId) return false;
return parseSessionUrlQuery(input.search).sessionId === input.sessionId;
}
/**
* Default `/` landing prefers an empty consultation so the starter home can
* center. Deep links and login-return URLs keep the requested session.
*/
export function resolveStarterHomeLandingSessionId(
sessions: readonly LandingSession[],
selectedId: string,
urlAction: LandingUrlAction,
): string {
if (urlAction === "keep" || urlAction === "replace-selected") return selectedId;
const selected = sessions.find((session) => session.id === selectedId);
if (selected?.sessionType !== "birth_time_rectification") return selectedId;
const emptyConsultation = sessions.find(
(session) => session.sessionType === "consultation" && session.messages.length === 0,
);
if (emptyConsultation) return emptyConsultation.id;
const anyConsultation = sessions.find((session) => session.sessionType === "consultation");
return anyConsultation?.id ?? selectedId;
}
export function starterHomeLandingNeedsConsultation(
sessions: readonly LandingSession[],
landingId: string,
urlAction: LandingUrlAction,
): boolean {
if (urlAction === "keep" || urlAction === "replace-selected") return false;
const landing = sessions.find((session) => session.id === landingId);
return !landing || landing.sessionType === "birth_time_rectification";
}
export const BOOTSTRAP_PREPARE_TIMEOUT_MS = 4000;
export const SESSION_PREFETCH_COUNT = 5;
@@ -19,7 +73,7 @@ export type BootstrapPrepareState = Readonly<{
dailyStarlanguageApplicable: boolean;
dailyStarlanguageSettled: boolean;
entrySummarySettled: boolean;
/** The selected session is a rectification session: its Case must be open and hydrated before the reveal. */
/** A URL-named rectification session must be opened and hydrated before reveal. */
rectificationApplicable?: boolean;
rectificationSettled?: boolean;
}>;