From b30308a5911b20a8998ad919c570d134e3f0bf15 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Mon, 20 Jul 2026 15:55:59 +0800 Subject: [PATCH] fix: harden mobile rectification recovery --- frontend/src/app/globals.css | 1 + .../src/hooks/use-birth-time-guided-journey.ts | 3 ++- frontend/src/lib/birth-time-user-error.ts | 9 +++++++++ .../birth-time-mobile-scroll-contract.test.ts | 10 ++++++++++ frontend/tests/birth-time-user-errors.test.ts | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/birth-time-user-error.ts create mode 100644 frontend/tests/birth-time-mobile-scroll-contract.test.ts create mode 100644 frontend/tests/birth-time-user-errors.test.ts diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 2c2c2c28..d4d179ba 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -208,6 +208,7 @@ button:disabled { cursor: default; opacity: .45; } } @media (max-width: 767px) { + .conversation.is-empty { display: block; } .brand-row { padding: 0 4px; } .brand-mark { width: 26px; height: 26px; } .session-nav { overflow: hidden; } diff --git a/frontend/src/hooks/use-birth-time-guided-journey.ts b/frontend/src/hooks/use-birth-time-guided-journey.ts index 81a08092..052419d3 100644 --- a/frontend/src/hooks/use-birth-time-guided-journey.ts +++ b/frontend/src/hooks/use-birth-time-guided-journey.ts @@ -20,6 +20,7 @@ import { saveGuidedBirthTimeCandidate, } from "@/lib/birth-time-guided-client"; import { confirmReviewedBirthTimeDraft } from "@/lib/birth-time-guided-draft-confirmation"; +import { birthTimeUserError } from "@/lib/birth-time-user-error"; import { claimMutation, createStableActionIdentityRegistry, @@ -198,7 +199,7 @@ export function useBirthTimeGuidedJourney(input: GuidedJourneyInput): BirthTimeG : completeGuidedBirthTimeCandidate({ caseId: turn.caseId, resultId, time }); void completion .then(() => onCandidateComplete(turn, time)) - .catch((caught) => setError(caught instanceof Error ? caught.message : "候选时间暂时无法保存")) + .catch((caught) => setError(birthTimeUserError(caught))) .finally(() => { release(); setPending(false); diff --git a/frontend/src/lib/birth-time-user-error.ts b/frontend/src/lib/birth-time-user-error.ts new file mode 100644 index 00000000..3e54aa69 --- /dev/null +++ b/frontend/src/lib/birth-time-user-error.ts @@ -0,0 +1,9 @@ +const unsafeImplementationMessage = /expected pattern|failed to fetch|networkerror|load failed|domexception|syntaxerror/i; + +export function birthTimeUserError(error: unknown): string { + const message = error instanceof Error ? error.message.trim() : ""; + if (!message || unsafeImplementationMessage.test(message) || !/[\u3400-\u9fff]/u.test(message)) { + return "候选时间暂时无法保存,请检查网络后重试。"; + } + return message; +} diff --git a/frontend/tests/birth-time-mobile-scroll-contract.test.ts b/frontend/tests/birth-time-mobile-scroll-contract.test.ts new file mode 100644 index 00000000..a60483fb --- /dev/null +++ b/frontend/tests/birth-time-mobile-scroll-contract.test.ts @@ -0,0 +1,10 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; + +const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); + +test("mobile rectification welcome content starts at the scroll origin", () => { + assert.match(css, /@media \(max-width: 767px\)[\s\S]*?\.conversation\.is-empty\s*\{[^}]*display:\s*block/); + assert.match(css, /\.conversation\s*\{[^}]*min-height:\s*0[^}]*overflow-y:\s*auto/); +}); diff --git a/frontend/tests/birth-time-user-errors.test.ts b/frontend/tests/birth-time-user-errors.test.ts new file mode 100644 index 00000000..2bbf1219 --- /dev/null +++ b/frontend/tests/birth-time-user-errors.test.ts @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { birthTimeUserError } from "../src/lib/birth-time-user-error.ts"; + +test("birth-time errors never expose browser implementation messages", () => { + assert.equal( + birthTimeUserError(new DOMException("The string did not match the expected pattern.")), + "候选时间暂时无法保存,请检查网络后重试。", + ); +}); + +test("birth-time errors preserve a safe server message", () => { + assert.equal(birthTimeUserError(new Error("候选结果已变化")), "候选结果已变化"); +});