fix(home): one new-chat intent and a scoped login-return stash (BUG-1038)

Coming back to / from /people could show the previous rectification session
as a locked page: its title in the header, the composer stuck on
"正在打开生时校正…", and the new-chat greeting in the middle.

The state that survived between pages is the sessionStorage login-return
stash that secondary-page sidebar links write from the current ?c=:
- /people「和 TA 对话」used a second intent (?newChat=1) parsed by a
  component mounted inside Home after bootstrap, so the bootstrap new-chat
  branch never ran and the stash won.
- A stash id not in the current person's loaded list was looked up and
  landed with urlAction "keep", which assumes ?c= is already in the address
  bar. It was not, so the rectification auto-open never fired. The stash
  also ignored which person was current.
- An in-page new chat left the stash in place.

Fix: delete NewChatDeepLink / ?newChat and route「和 TA 对话」through
newChatHref(); a looked-up stash writes ?c= back (replace-selected) and is
dropped when it belongs to another person; startNewChat and
openChatBoundToProfile clear the stash. ?c= deep links, BUG-989 and BUG-705
are unchanged.

Tests: new real-lifecycle suite mounting the real Home, sidebar and people
page (10 cases: four secondary pages + mobile drawer, 和 TA 对话 for self and
another person, out-of-scope stash, same-person stash beyond the first page,
in-page new chat), plus two contract/unit tests. Six fail on origin/staging,
all pass here. Full suite 3928 / 61 failing, failure names identical to the
0ab061b9 baseline (3916 / 61).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-26 07:23:50 +08:00
co-authored by Claude Opus 5.5
parent f4b8f26e15
commit 0a8350cc01
19 changed files with 765 additions and 24 deletions
@@ -2,7 +2,6 @@
import { useEffect, useRef } from "react";
import { useCurrentSubjectReady } from "@/lib/current-subject";
import type { ChartLibraryRecord, Profile, SynastryRelationshipType } from "@/lib/home-types";
function stripQuery(key: string) {
@@ -13,22 +12,6 @@ function stripQuery(key: string) {
window.history.replaceState(null, "", `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`);
}
export function NewChatDeepLink(props: {
ready: boolean;
startNewChat: () => Promise<unknown>;
}) {
const started = useRef(false);
const subjectReady = useCurrentSubjectReady();
useEffect(() => {
if (!props.ready || !subjectReady || started.current) return;
if (!new URLSearchParams(window.location.search).has("newChat")) return;
started.current = true;
stripQuery("newChat");
void props.startNewChat();
}, [props.ready, props.startNewChat, subjectReady]);
return null;
}
export function SynastryDeepLink(props: {
profile: Profile;
draft: (record: ChartLibraryRecord, relationshipType: SynastryRelationshipType) => Promise<void>;
@@ -3,6 +3,7 @@
import { useEffect, useRef, useState, type Dispatch, type FormEvent, type SetStateAction } from "react";
import { FileText, MessageSquareText, Orbit } from "lucide-react";
import { newChatHref } from "@/lib/chat-session-url";
import { effectiveBirthDate } from "@/lib/effective-birth-date";
import { ChartProfileForm } from "@/components/chart-profile-form";
import { SecondaryPageShell } from "@/components/secondary-page-shell";
@@ -434,7 +435,7 @@ export function PeopleArchiveView({
<dd>{peopleAyanamsaLabel(selected.profile)}</dd>
</dl>
<div className="people-archive-actions">
<button type="button" onClick={() => openPerson(selected.id, "/?newChat=1")}>
<button type="button" onClick={() => openPerson(selected.id, newChatHref())}>
<MessageSquareText size={18} strokeWidth={1.75} aria-hidden="true" />和 TA 对话
</button>
<button type="button" onClick={() => openPerson(selected.id, "/chart")}>