refactor(home): 校正面 15 个状态从 Home 下沉
Independent Staging Quality Gate / validate (push) Failing after 6m43s
Independent Staging Quality Gate / publish (push) Skipped

Home() useState 66->52, useRef 41->39. Shell-facing fields merge into one object;
subtree state lives in useRectificationSurface. Chat props 20->7. Growth contract
rebaselined. Zero behavior/copy change.
This commit is contained in:
jesse-ux
2026-09-16 08:33:48 +08:00
parent 3b17c1b242
commit f8e607c29a
10 changed files with 448 additions and 122 deletions
+120 -42
View File
@@ -1,6 +1,6 @@
"use client";
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
import { useEffect, useRef, useState, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
import { resolveSessionTitle } from "@/lib/agent-reply";
import { rectificationOpenIdentity } from "@/lib/rectification-session-open";
@@ -27,7 +27,10 @@ import {
openResponseFromPayload,
type RectificationEntrySummary,
} from "@/lib/rectification-entry";
import type { PersistedRectificationTurn } from "@/components/conversational-birth-time-rectification";
import type {
PersistedRectificationTurn,
RectificationChatPanel,
} from "@/components/conversational-birth-time-rectification";
import type { PublicLanguageModelCatalog } from "@/lib/public-models";
import {
hydrateRectificationCase,
@@ -38,6 +41,60 @@ import {
type RectificationCaseSnapshotPayload,
} from "@/lib/rectification-surface-state";
export type RectificationShellState = {
sessionId: string | null;
caseId: string | null;
openingSessionId: string | null;
loading: boolean;
mutationPending: boolean;
entrySummary: RectificationEntrySummary | null;
entrySummarySettled: boolean;
error: string;
errorSessionId: string | null;
};
export const EMPTY_RECTIFICATION_SHELL: RectificationShellState = {
sessionId: null,
caseId: null,
openingSessionId: null,
loading: false,
mutationPending: false,
entrySummary: null,
entrySummarySettled: false,
error: "",
errorSessionId: null,
};
function fieldSetter<K extends keyof RectificationShellState>(
setRectification: Dispatch<SetStateAction<RectificationShellState>>,
key: K,
): Dispatch<SetStateAction<RectificationShellState[K]>> {
return (value) => {
setRectification((current) => ({
...current,
[key]: typeof value === "function"
? (value as (prev: RectificationShellState[K]) => RectificationShellState[K])(current[key])
: value,
}));
};
}
export function createRectificationShellSetters(
setRectification: Dispatch<SetStateAction<RectificationShellState>>,
) {
return {
setRectificationSessionId: fieldSetter(setRectification, "sessionId"),
setRectificationCaseId: fieldSetter(setRectification, "caseId"),
setRectificationOpeningSessionId: fieldSetter(setRectification, "openingSessionId"),
setRectificationLoading: fieldSetter(setRectification, "loading"),
setRectificationMutationPending: fieldSetter(setRectification, "mutationPending"),
setRectificationEntrySummary: fieldSetter(setRectification, "entrySummary"),
setRectificationEntrySummarySettled: fieldSetter(setRectification, "entrySummarySettled"),
setRectificationError: fieldSetter(setRectification, "error"),
setRectificationErrorSessionId: fieldSetter(setRectification, "errorSessionId"),
};
}
export type RectificationSurfaceParams = {
account: Account | null;
activeChartId: string;
@@ -47,11 +104,8 @@ export type RectificationSurfaceParams = {
modelCatalog: PublicLanguageModelCatalog | null;
persistSession: (session: ChatSession, mode?: "create" | "update") => Promise<void>;
profile: Profile;
rectificationLoading: boolean;
rectificationMutationPending: boolean;
rectificationOpenInFlight: MutableRefObject<boolean>;
rectificationSessionId: string | null;
resumeRectificationSession: MutableRefObject<(session: ChatSession) => void>;
rectification: RectificationShellState;
setRectification: Dispatch<SetStateAction<RectificationShellState>>;
sessionSelectionSource: MutableRefObject<"user" | "history">;
sessions: ChatSession[];
setActiveSessionId: Dispatch<SetStateAction<string>>;
@@ -60,18 +114,6 @@ export type RectificationSurfaceParams = {
setDraftTheme: (theme: Theme | null) => void;
setOnboardingStep: Dispatch<SetStateAction<OnboardingStep>>;
setProfileNotice: Dispatch<SetStateAction<string>>;
setRectificationCaseId: Dispatch<SetStateAction<string | null>>;
setRectificationEntrySummary: Dispatch<SetStateAction<RectificationEntrySummary | null>>;
setRectificationError: Dispatch<SetStateAction<string>>;
setRectificationErrorSessionId: Dispatch<SetStateAction<string | null>>;
setRectificationLoading: Dispatch<SetStateAction<boolean>>;
setRectificationPendingQuestion: Dispatch<SetStateAction<string | null>>;
setRectificationReadonly: Dispatch<SetStateAction<boolean>>;
setRectificationSessionId: Dispatch<SetStateAction<string | null>>;
setRectificationShouldStartOpening: Dispatch<SetStateAction<boolean>>;
setRectificationSnapshot: Dispatch<SetStateAction<RectificationCaseSnapshotPayload | null>>;
setRectificationOpeningSessionId: Dispatch<SetStateAction<string | null>>;
setRectificationTurns: Dispatch<SetStateAction<PersistedRectificationTurn[]>>;
setSessions: Dispatch<SetStateAction<ChatSession[]>>;
uiPreview: MutableRefObject<boolean>;
updateSession: (sessionId: string, change: (session: ChatSession) => ChatSession) => void;
@@ -90,11 +132,8 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
modelCatalog,
persistSession,
profile,
rectificationLoading,
rectificationMutationPending,
rectificationOpenInFlight,
rectificationSessionId,
resumeRectificationSession,
rectification,
setRectification,
sessionSelectionSource,
sessions,
setActiveSessionId,
@@ -103,18 +142,6 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
setDraftTheme,
setOnboardingStep,
setProfileNotice,
setRectificationCaseId,
setRectificationEntrySummary,
setRectificationError,
setRectificationErrorSessionId,
setRectificationLoading,
setRectificationPendingQuestion,
setRectificationReadonly,
setRectificationSessionId,
setRectificationShouldStartOpening,
setRectificationSnapshot,
setRectificationOpeningSessionId,
setRectificationTurns,
setSessions,
uiPreview,
updateSession,
@@ -123,6 +150,29 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
rectificationSessionOpenerRef,
} = params;
const rectificationSessionId = rectification.sessionId;
const rectificationCaseId = rectification.caseId;
const rectificationLoading = rectification.loading;
const rectificationMutationPending = rectification.mutationPending;
const setRectificationSessionId = fieldSetter(setRectification, "sessionId");
const setRectificationCaseId = fieldSetter(setRectification, "caseId");
const setRectificationOpeningSessionId = fieldSetter(setRectification, "openingSessionId");
const setRectificationLoading = fieldSetter(setRectification, "loading");
const setRectificationMutationPending = fieldSetter(setRectification, "mutationPending");
const setRectificationEntrySummary = fieldSetter(setRectification, "entrySummary");
const setRectificationError = fieldSetter(setRectification, "error");
const setRectificationErrorSessionId = fieldSetter(setRectification, "errorSessionId");
const [rectificationHeaderSlot, setRectificationHeaderSlot] = useState<HTMLElement | null>(null);
const [rectificationPendingQuestion, setRectificationPendingQuestion] = useState<string | null>(null);
const [rectificationReadonly, setRectificationReadonly] = useState(false);
const [rectificationShouldStartOpening, setRectificationShouldStartOpening] = useState(false);
const [rectificationTurns, setRectificationTurns] = useState<PersistedRectificationTurn[]>([]);
const [rectificationSnapshot, setRectificationSnapshot] = useState<RectificationCaseSnapshotPayload | null>(null);
const rectificationOpenInFlight = useRef(false);
const resumeRectificationSession = useRef<(session: ChatSession) => void>(() => undefined);
async function refreshRectificationEntrySummary() {
if (!account) return;
try {
@@ -289,12 +339,6 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
await openRectificationCase("new", null, null);
}
resumeRectificationSession.current = (session) => {
sessionSelectionSource.current = "history";
void openRectificationSession(session.id);
};
rectificationSessionOpenerRef.current = openRectificationSession;
function handleRectificationProfileIncomplete() {
setRectificationError("profile_incomplete");
setRectificationSessionId(null);
@@ -320,7 +364,41 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
}));
}
useEffect(() => {
resumeRectificationSession.current = (session) => {
sessionSelectionSource.current = "history";
void openRectificationSession(session.id);
};
rectificationSessionOpenerRef.current = openRectificationSession;
});
const rectificationPanel: RectificationChatPanel = {
caseId: rectificationCaseId ?? "",
sessionId: rectificationSessionId ?? "",
readonly: rectificationReadonly,
shouldStartOpening: rectificationShouldStartOpening,
initialTurns: rectificationTurns,
initialSnapshot: rectificationSnapshot,
pendingConsultationQuestion: rectificationPendingQuestion,
headerSlot: rectificationHeaderSlot,
onMessagesChange: handleRectificationMessagesChange,
onCompleted: () => {
void refreshAccount();
if (rectificationCaseId && rectificationSessionId) {
void refreshRectificationCase(rectificationCaseId, rectificationSessionId);
}
},
onPendingChange: setRectificationMutationPending,
onProfileIncomplete: handleRectificationProfileIncomplete,
onOpeningConsumed: () => setRectificationShouldStartOpening(false),
onRestart: () => void startNewRectification(),
};
return {
rectificationHeaderSlot,
setRectificationHeaderSlot,
rectificationPanel,
resumeRectificationSession,
refreshRectificationEntrySummary,
refreshRectificationCase,
openRectificationCase,