Product decision 2026-09-27 (D1-D4), overriding the BUG-1038 login-return stash and the "latest session" default landing: - A full load of / without ?c= (login, typed address, bookmark, refresh of bare /) lands on the current person's blank starter home; an existing empty draft of that person is reused, otherwise one is created locally. - ?c= and ?new=1 are unchanged; a refresh inside a conversation keeps its ?c=. - The login-return stash is removed: redirectToLogin and sidebar links no longer write it, bootstrap no longer reads it and clears a leftover value once. replace-selected, the lookup origin/other-subject branch and the warm resumeRectification dependency go with it. - A background answer being recovered no longer takes over the blank home (same rule BUG-1015 set for ?new=1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
117 lines
4.7 KiB
TypeScript
117 lines
4.7 KiB
TypeScript
export const SESSION_URL_QUERY_KEY = "c";
|
|
export const NEW_CHAT_QUERY_KEY = "new";
|
|
/**
|
|
* Legacy key only. Until BUG-1052 a 401 or a secondary-page link stashed the
|
|
* current `?c=` here and a bare `/` reopened it. Nothing writes it any more;
|
|
* bootstrap removes a leftover value so an old tab cannot keep a stale id.
|
|
*/
|
|
export const SESSION_URL_RETURN_STORAGE_KEY = "jyotisha.session-url-return";
|
|
export const SESSION_MISSING_NOTICE = "该对话不存在或已被删除";
|
|
export const SESSION_LOOKUP_FAILED_NOTICE = "这条对话暂时读不到,请稍后重试。";
|
|
export const SESSION_URL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
export type SessionUrlQuery = {
|
|
readonly present: boolean;
|
|
readonly sessionId: string | null;
|
|
};
|
|
|
|
export type BootstrapSessionSelection = {
|
|
readonly sessionId: string;
|
|
readonly urlAction: "keep" | "replace-clear" | "none" | "lookup" | "new-chat";
|
|
readonly missing: boolean;
|
|
};
|
|
|
|
/**
|
|
* The one new-chat intent. The read-only sidebar and /people「和 TA 对话」 both
|
|
* link here; the person rides on the current-subject store, not the URL.
|
|
* Bootstrap turns it into a local empty consultation, so no older session is
|
|
* reopened (BUG-1015/1038).
|
|
*/
|
|
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)) {
|
|
return { present: false, sessionId: null };
|
|
}
|
|
const raw = params.get(SESSION_URL_QUERY_KEY) ?? "";
|
|
return {
|
|
present: true,
|
|
sessionId: SESSION_URL_ID_PATTERN.test(raw) ? raw : null,
|
|
};
|
|
}
|
|
|
|
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();
|
|
const path = pathname || "/";
|
|
return query ? `${path}?${query}` : path;
|
|
}
|
|
|
|
export function writeSessionUrl(sessionId: string | null, mode: "push" | "replace") {
|
|
const { location, history } = globalThis.window;
|
|
const next = sessionHref(location.search, sessionId, location.pathname);
|
|
const current = `${location.pathname}${location.search}`;
|
|
if (current === next) return;
|
|
if (mode === "push") history.pushState(null, "", next);
|
|
else history.replaceState(null, "", next);
|
|
}
|
|
|
|
/** Removes a stash an older build left behind (BUG-1052). Never read back. */
|
|
export function clearLoginSessionReturn() {
|
|
try {
|
|
globalThis.sessionStorage.removeItem(SESSION_URL_RETURN_STORAGE_KEY);
|
|
} catch {
|
|
// Ignore private-mode quota errors.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Where a full load of `/` lands (BUG-1052): `?new=1` → a new chat; `?c=` →
|
|
* that conversation (shared links, sidebar links, refresh inside a
|
|
* conversation); anything else — login, a typed address, a bookmark, a
|
|
* refresh of bare `/` — → the blank starter home. There is no stored
|
|
* "return to the last conversation" any more.
|
|
*/
|
|
export function resolveBootstrapSessionSelection(input: {
|
|
readonly listedIds: readonly string[];
|
|
readonly defaultSessionId: string;
|
|
readonly search: string;
|
|
}): BootstrapSessionSelection {
|
|
if (parseNewChatIntent(input.search)) {
|
|
return { sessionId: input.defaultSessionId, urlAction: "new-chat", missing: false };
|
|
}
|
|
const query = parseSessionUrlQuery(input.search);
|
|
if (query.present) {
|
|
if (query.sessionId && input.listedIds.includes(query.sessionId)) {
|
|
return { sessionId: query.sessionId, urlAction: "keep", missing: false };
|
|
}
|
|
if (query.sessionId) {
|
|
return { sessionId: query.sessionId, urlAction: "lookup", missing: false };
|
|
}
|
|
return { sessionId: input.defaultSessionId, urlAction: "replace-clear", missing: true };
|
|
}
|
|
return { sessionId: input.defaultSessionId, urlAction: "none", missing: false };
|
|
}
|
|
|
|
/** Outcome of looking up a `?c=` id that is not in the loaded page (BUG-705). */
|
|
export function bootstrapSelectionFromLookup(
|
|
status: "found" | "missing" | "unavailable",
|
|
requestedId: string,
|
|
defaultSessionId: string,
|
|
): BootstrapSessionSelection {
|
|
if (status === "found") return { sessionId: requestedId, urlAction: "keep", missing: false };
|
|
if (status === "missing") return { sessionId: defaultSessionId, urlAction: "replace-clear", missing: true };
|
|
return { sessionId: defaultSessionId, urlAction: "none", missing: false };
|
|
}
|