From 308e5d532ae10846ef196cf21eea3efc49a843dd Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sun, 19 Jul 2026 21:59:43 +0800 Subject: [PATCH] fix: scope onboarding fallback greeting --- frontend/src/app/page.tsx | 3 +- frontend/src/lib/onboarding-client.ts | 4 +++ frontend/tests/onboarding-client.test.ts | 26 -------------- .../tests/onboarding-presentation.test.ts | 35 +++++++++++++++++++ 4 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 frontend/tests/onboarding-presentation.test.ts diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 22fb49d2..a020968d 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -46,6 +46,7 @@ import { chatMessageViews, type ChatMessage } from "@/lib/chat-message-view"; import { OnboardingAuthenticationError, type OnboardingContent, + createOnboardingFallbackGreeting, createStartGreeting, isCurrentOnboardingRequest, onboardingProfileFingerprint, @@ -2320,7 +2321,7 @@ export default function Home() { + : createOnboardingFallbackGreeting(profile.name))} /> )} {!profileComplete && onboardingStep === "birth" && onboardingCardReady && ( diff --git a/frontend/src/lib/onboarding-client.ts b/frontend/src/lib/onboarding-client.ts index 67ba8b73..7f36fed7 100644 --- a/frontend/src/lib/onboarding-client.ts +++ b/frontend/src/lib/onboarding-client.ts @@ -104,6 +104,10 @@ export function createStartGreeting( return variants[Math.floor(variantSelection * variants.length)](displayName); } +export function createOnboardingFallbackGreeting(name: string): string { + return `${name.trim()},从你此刻最关心的问题开始吧。`; +} + export function onboardingProfileFingerprint(profile: OnboardingProfileFingerprintInput): string { return JSON.stringify([ profile.name, diff --git a/frontend/tests/onboarding-client.test.ts b/frontend/tests/onboarding-client.test.ts index ca6c0684..393776de 100644 --- a/frontend/tests/onboarding-client.test.ts +++ b/frontend/tests/onboarding-client.test.ts @@ -3,10 +3,6 @@ import test from "node:test"; import { OnboardingAuthenticationError, OnboardingRequestError, - createStartGreeting, - isCurrentOnboardingRequest, - onboardingProfileFingerprint, - onboardingRequestIdentity, requestOnboardingWithRecovery, } from "../src/lib/onboarding-client.ts"; @@ -20,28 +16,6 @@ const personalizedOnboarding = { source: "cache", } as const; -const completeProfile = { - name: "林遥", date: "1990-06-15", time: "12:30", reportedTime: "12:30", - birthTimeSource: "hospital_record", birthTimePeriod: "", birthTimeClue: "出生证明", birthTimeStatus: "confirmed", - uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, rectificationCaseId: "", countryCode: "CN", provinceCode: "110000", cityCode: "110000-city", districtCode: "110101", -} as const; - -test("rejects a stale completion after a complete profile changes", () => { - // Given: one account starts onboarding for a complete persisted profile. - const firstIdentity = onboardingRequestIdentity("account-1", onboardingProfileFingerprint(completeProfile)); - - // When: a presentation-affecting profile field changes before that request completes. - const changedFingerprint = onboardingProfileFingerprint({ ...completeProfile, name: "周宁" }); - const currentIdentity = onboardingRequestIdentity("account-1", changedFingerprint); - const committedGreeting = createStartGreeting("周宁", new Date("2026-07-19T08:00:00+08:00"), 0); - - // Then: the new profile has a distinct identity and its greeting cannot retain the old name. - assert.notEqual(currentIdentity, firstIdentity); - assert.equal(isCurrentOnboardingRequest(currentIdentity, firstIdentity), false); - assert.match(committedGreeting, /周宁/); - assert.doesNotMatch(committedGreeting, /林遥/); -}); - test("returns personalized cache content after a timeout and pending response", async () => { // Given: the first request times out, the second is provisional, and the third is terminal. const originalFetch = globalThis.fetch; diff --git a/frontend/tests/onboarding-presentation.test.ts b/frontend/tests/onboarding-presentation.test.ts new file mode 100644 index 00000000..cee1a3c7 --- /dev/null +++ b/frontend/tests/onboarding-presentation.test.ts @@ -0,0 +1,35 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + createOnboardingFallbackGreeting, + createStartGreeting, + isCurrentOnboardingRequest, + onboardingProfileFingerprint, + onboardingRequestIdentity, +} from "../src/lib/onboarding-client.ts"; + +const completeProfile = { + name: "林遥", date: "1990-06-15", time: "12:30", reportedTime: "12:30", + birthTimeSource: "hospital_record", birthTimePeriod: "", birthTimeClue: "出生证明", birthTimeStatus: "confirmed", + uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, rectificationCaseId: "", countryCode: "CN", provinceCode: "110000", cityCode: "110000-city", districtCode: "110101", +} as const; + +test("rejects stale A presentation and derives terminal and fallback greetings from profile B", () => { + // Given: one account starts onboarding for a complete persisted profile A. + const firstIdentity = onboardingRequestIdentity("account-1", onboardingProfileFingerprint(completeProfile)); + + // When: the name changes before that request completes and profile B is presented. + const changedProfile = { ...completeProfile, name: "周宁" }; + const currentIdentity = onboardingRequestIdentity("account-1", onboardingProfileFingerprint(changedProfile)); + const terminalGreeting = createStartGreeting(changedProfile.name, new Date("2026-07-19T08:00:00+08:00"), 0); + const fallbackGreeting = createOnboardingFallbackGreeting(changedProfile.name); + + // Then: stale work is rejected and neither presentation path can retain A's name. + assert.notEqual(currentIdentity, firstIdentity); + assert.equal(isCurrentOnboardingRequest(currentIdentity, firstIdentity), false); + for (const greeting of [terminalGreeting, fallbackGreeting]) { + assert.match(greeting, /周宁/); + assert.doesNotMatch(greeting, /林遥/); + } + assert.equal(fallbackGreeting, "周宁,从你此刻最关心的问题开始吧。"); +});