fix(chat): honor new-chat intent from secondary pages
Independent Staging Quality Gate / validate (push) Successful in 13m41s
Independent Staging Quality Gate / publish (push) Successful in 3m30s

Create a fresh local consultation for explicit new-chat navigation and keep reserved recovery from taking over its landing. Add regression tests and record validation gaps for remote review.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-24 00:25:28 +08:00
co-authored by Claude Code
parent 1c95eb3979
commit 8902e48468
17 changed files with 368 additions and 6 deletions
+5
View File
@@ -1062,6 +1062,7 @@ export default function Home() {
setStartGreeting(nextProfile.name.trim() ? createStartGreeting(nextProfile.name) : "");
setOnboardingStep(missingProfileStep(nextProfile) ?? "name");
setSessions(nextSessions);
if (bootstrapSelection.urlAction === "new-chat") writeSessionUrl(null, "replace");
setActiveSessionId(landingSessionId);
if (bootstrapSelection.clearStoredReturn) clearLoginSessionReturn();
if (bootstrapSelection.urlAction === "replace-clear") writeSessionUrl(null, "replace");
@@ -1074,6 +1075,10 @@ export default function Home() {
if (reservedConsultation?.status === "reserved") {
const recoverySession = nextSessions.find((session) => session.id === reservedConsultation.sessionId);
if (recoverySession) restoreConsultationRecovery(recoverySession, reservedConsultation.requestId, storedPending);
if (bootstrapSelection.urlAction === "new-chat") {
setActiveSessionId(landingSessionId);
setComposerNotice("");
}
}
if (modelCatalogResult.unavailable) {
setComposerNotice("模型服务暂时不可用,当前无法发送问题。");
+2 -2
View File
@@ -19,7 +19,7 @@ import { usePathname } from "next/navigation";
import { useEffect, useRef } from "react";
import type { Ref } from "react";
import { AppLink } from "@/components/app-link";
import { persistLoginSessionReturn, sessionHref } from "@/lib/chat-session-url";
import { newChatHref, persistLoginSessionReturn, sessionHref } from "@/lib/chat-session-url";
import { prefetchSecondaryPage } from "@/lib/secondary-page-data";
import {
Sidebar,
@@ -248,7 +248,7 @@ export function AppSidebar({
{showExpandedContent ? <span>{creatingSession ? "正在创建" : "新建对话"}</span> : null}
</SidebarMenuButton>
) : (
<SidebarMenuLink className="new-chat" tooltip="新对话" href="/" onClick={closeDrawer}>
<SidebarMenuLink className="new-chat" tooltip="新对话" href={newChatHref()} onClick={closeDrawer}>
<SquarePen size={18} strokeWidth={1.75} aria-hidden="true" />
{showExpandedContent ? <span>新建对话</span> : null}
</SidebarMenuLink>
+20 -1
View File
@@ -1,4 +1,5 @@
export const SESSION_URL_QUERY_KEY = "c";
export const NEW_CHAT_QUERY_KEY = "new";
export const SESSION_URL_RETURN_STORAGE_KEY = "jyotisha.session-url-return";
export const SESSION_MISSING_NOTICE = "该对话不存在或已被删除";
export const SESSION_LOOKUP_FAILED_NOTICE = "这条对话暂时读不到,请稍后重试。";
@@ -11,11 +12,20 @@ export type SessionUrlQuery = {
export type BootstrapSessionSelection = {
readonly sessionId: string;
readonly urlAction: "keep" | "replace-selected" | "replace-clear" | "none" | "lookup";
readonly urlAction: "keep" | "replace-selected" | "replace-clear" | "none" | "lookup" | "new-chat";
readonly missing: boolean;
readonly clearStoredReturn: boolean;
};
export function newChatHref(): string {
return "/?new=1";
}
export function parseNewChatIntent(search: string): boolean {
const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
return params.has(NEW_CHAT_QUERY_KEY);
}
export function parseSessionUrlQuery(search: string): SessionUrlQuery {
const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
if (!params.has(SESSION_URL_QUERY_KEY)) {
@@ -30,6 +40,7 @@ export function parseSessionUrlQuery(search: string): SessionUrlQuery {
export function sessionHref(search: string, sessionId: string | null, pathname = "/"): string {
const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
params.delete(NEW_CHAT_QUERY_KEY);
if (sessionId) params.set(SESSION_URL_QUERY_KEY, sessionId);
else params.delete(SESSION_URL_QUERY_KEY);
const query = params.toString();
@@ -79,6 +90,14 @@ export function resolveBootstrapSessionSelection(input: {
readonly search: string;
readonly storedReturnId: string | null;
}): BootstrapSessionSelection {
if (parseNewChatIntent(input.search)) {
return {
sessionId: input.defaultSessionId,
urlAction: "new-chat",
missing: false,
clearStoredReturn: true,
};
}
const query = parseSessionUrlQuery(input.search);
if (query.present) {
if (query.sessionId && input.listedIds.includes(query.sessionId)) {
+3 -2
View File
@@ -18,7 +18,7 @@ export type LandingSession = Readonly<{
messages: readonly unknown[];
}>;
export type LandingUrlAction = "keep" | "replace-selected" | "replace-clear" | "none" | "lookup";
export type LandingUrlAction = "keep" | "replace-selected" | "replace-clear" | "none" | "lookup" | "new-chat";
/**
* A rectification Case is opened only when the address bar names that session
@@ -43,7 +43,7 @@ export function resolveStarterHomeLandingSessionId(
selectedId: string,
urlAction: LandingUrlAction,
): string {
if (urlAction === "keep" || urlAction === "replace-selected" || urlAction === "lookup") {
if (urlAction === "keep" || urlAction === "replace-selected" || urlAction === "lookup" || urlAction === "new-chat") {
return selectedId;
}
const selected = sessions.find((session) => session.id === selectedId);
@@ -61,6 +61,7 @@ export function starterHomeLandingNeedsConsultation(
landingId: string,
urlAction: LandingUrlAction,
): boolean {
if (urlAction === "new-chat") return true;
if (urlAction === "keep" || urlAction === "replace-selected" || urlAction === "lookup") return false;
const landing = sessions.find((session) => session.id === landingId);
return !landing || landing.sessionType === "birth_time_rectification";