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
+1 -1
View File
@@ -117,7 +117,7 @@ test("default bootstrap selection does not write a session URL", () => {
const bootstrap = sourceBetween(page, "async function loadCloudData()", "void loadCloudData();");
assert.match(bootstrap, /defaultSessionId: nextSessions\[0\]\.id/);
assert.match(bootstrap, /setActiveSessionId\(bootstrapSelection\.sessionId\)/);
assert.match(bootstrap, /setActiveSessionId\(landingSessionId\)/);
assert.match(bootstrap, /urlAction === "replace-clear"/);
assert.match(bootstrap, /urlAction === "replace-selected"/);
assert.doesNotMatch(bootstrap, /writeSessionUrl\([^)]*, "push"\)/);
@@ -8,7 +8,10 @@ import {
bootstrapLoadingCopy,
bootstrapPrepareSettled,
bootstrapRevealDelayMs,
resolveStarterHomeLandingSessionId,
sessionIdsToPrefetch,
shouldAutoOpenRectificationSession,
starterHomeLandingNeedsConsultation,
} from "../src/lib/home-bootstrap.ts";
import { homeSurface } from "./home-surface.ts";
@@ -22,6 +25,65 @@ const starterHome = readFileSync(new URL("../src/components/starter-home.tsx", i
const globalsCss = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const staleClientRecovery = readFileSync(new URL("../src/components/stale-client-recovery.tsx", import.meta.url), "utf8");
const rectificationId = "11111111-1111-4111-8111-111111111111";
const consultationId = "22222222-2222-4222-8222-222222222222";
const emptyConsultationId = "33333333-3333-4333-8333-333333333333";
test("bare / after login does not auto-open the latest rectification session", () => {
assert.equal(shouldAutoOpenRectificationSession({
sessionType: "birth_time_rectification",
sessionId: rectificationId,
search: "",
}), false);
assert.equal(shouldAutoOpenRectificationSession({
sessionType: "birth_time_rectification",
sessionId: rectificationId,
search: `?c=${rectificationId}`,
}), true);
assert.equal(shouldAutoOpenRectificationSession({
sessionType: "birth_time_rectification",
sessionId: rectificationId,
search: `?c=${consultationId}`,
}), false);
assert.equal(shouldAutoOpenRectificationSession({
sessionType: "consultation",
sessionId: consultationId,
search: `?c=${consultationId}`,
}), false);
});
test("default landing replaces a latest rectification session with an empty consultation", () => {
const sessions = [
{ id: rectificationId, sessionType: "birth_time_rectification", messages: [] },
{ id: emptyConsultationId, sessionType: "consultation", messages: [] },
{ id: consultationId, sessionType: "consultation", messages: [{ role: "user" }] },
];
assert.equal(resolveStarterHomeLandingSessionId(sessions, rectificationId, "none"), emptyConsultationId);
assert.equal(resolveStarterHomeLandingSessionId(sessions, rectificationId, "replace-clear"), emptyConsultationId);
assert.equal(resolveStarterHomeLandingSessionId(sessions, rectificationId, "keep"), rectificationId);
assert.equal(resolveStarterHomeLandingSessionId(sessions, rectificationId, "replace-selected"), rectificationId);
assert.equal(resolveStarterHomeLandingSessionId(sessions, consultationId, "none"), consultationId);
assert.equal(
starterHomeLandingNeedsConsultation(
[{ id: rectificationId, sessionType: "birth_time_rectification", messages: [] }],
rectificationId,
"none",
),
true,
);
assert.equal(
starterHomeLandingNeedsConsultation(sessions, emptyConsultationId, "none"),
false,
);
assert.equal(
starterHomeLandingNeedsConsultation(sessions, rectificationId, "keep"),
false,
);
assert.match(page, /resolveStarterHomeLandingSessionId\(/);
assert.match(page, /shouldAutoOpenRectificationSession\(/);
assert.match(page, /starterHomeLandingNeedsConsultation\(/);
});
test("prepare phase settles only when every applicable item has an answer", () => {
const base = {
profileComplete: true,
@@ -45,11 +45,13 @@ test("the rectification surface is revealed once: Case hydration precedes the sw
assert.match(chat, /useEffect\(\(\) => \(\) => \{\s*runAbort\.current\?\.abort\(\);\s*snapshotAbort\.current\?\.abort\(\);\s*\}, \[\]\);/);
});
test("a rectification session selected at bootstrap is hydrated before the reveal, and popstate defers the same way", () => {
test("a rectification session named in the URL is hydrated before the reveal; bare / does not", () => {
// The resume effect runs during the prepare phase, not only after the reveal.
assert.match(page, /if \(\(!hydrated && bootstrapPhase === "account"\)\s*\|\| !account/);
assert.match(page, /shouldAutoOpenRectificationSession\(\{/);
assert.match(bootstrap, /if \(state\.rectificationApplicable && !state\.rectificationSettled\) return false;/);
assert.match(page, /rectificationApplicable: activeSession\?\.sessionType === "birth_time_rectification",\s*rectificationSettled: activeSession\?\.id === rectificationSessionId\s*\|\| rectificationError !== ""\s*\|\| !profileComplete,/);
assert.match(page, /rectificationApplicable: shouldAutoOpenRectificationSession\(\{/);
assert.match(page, /rectificationSettled: activeSession\?\.id === rectificationSessionId\s*\|\| rectificationError !== ""\s*\|\| !profileComplete,/);
// One deadline constant for hydration and the home reveal.
assert.match(read("../src/lib/rectification-surface-state.ts"), /export const RECTIFICATION_OPEN_HYDRATE_TIMEOUT_MS = BOOTSTRAP_PREPARE_TIMEOUT_MS;/);
// History navigation goes through selectSession, which defers the switch (locked above).
@@ -22,6 +22,16 @@ test("keeps onboarding transcript and intake card on the same session column", (
assert.match(globalStyles, /\.conversation\.is-onboarding-form \.welcome \{[\s\S]*padding:\s*var\(--space-6\) var\(--session-column-gutter\) var\(--space-8\)/);
});
test("centers the starter home even when the conversation grid is not is-empty", () => {
const pageSource = readProjectFile("src/app/page.tsx");
assert.match(globalStyles, /\.welcome \{\s*width:\s*min\(1040px, 100%\);\s*margin-inline:\s*auto;/);
assert.match(globalStyles, /\.starter-list \{\s*width:\s*min\(1040px, 100%\);\s*margin-inline:\s*auto;/);
assert.match(
pageSource,
/!onboardingFormActive && !activeSession\?\.messages\.length && \(Boolean\(activeSession\?\.messagesHydrated\) \|\| activeSession\?\.sessionType !== "consultation"\) \? " is-empty"/,
);
});
test("keeps message motion restrained and honors reduced-motion preferences", () => {
assert.match(messageRowSource, /gsap\.matchMedia\(\)/);
assert.match(messageRowSource, /prefers-reduced-motion:\s*no-preference/);