Compare commits
2 Commits
6757ad405d
...
036a756be3
| Author | SHA1 | Date | |
|---|---|---|---|
| 036a756be3 | |||
| f4619df273 |
@@ -5099,6 +5099,22 @@
|
||||
- 复发自:BUG-204(报告页 chunk 偏斜已修,首页引导层仍会把同一失败画成永久 loading)
|
||||
- 修复版本:50e1a4ac
|
||||
|
||||
## BUG-339 | staging quality gate 因盘面入口 effect 同步 setState 失败
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-21
|
||||
- 最近更新:2026-08-21
|
||||
- 影响面:Gitea `backend-quality-gate` `validate`、`rectification-agentic-chat` 窄屏盘面入口 portal
|
||||
- 用户现象:向 `staging` 推送后 quality gate 失败,镜像未发布,站点仍停在上一版。日志是 `react-hooks/set-state-in-effect`。
|
||||
- 触发条件:`npm run lint --prefix frontend`。自 `8b8e5214` 起,gate run 2019/2020/2021 均因此失败。
|
||||
- 根因:窄屏盘面入口用 `useLayoutEffect` 里 `querySelector` 后立刻 `setHeaderSlot`。`eslint-config-next` 禁止在 effect 同步 setState。BUG-335 把入口 portal 到页头时引入该写法。
|
||||
- 修复:页头挂载点改用 callback ref,把 DOM 节点作为 `headerSlot` 传给校时会话。仍 `createPortal` 到 `data-rectification-header-slot`,不再在 effect 里查 DOM 或 setState。
|
||||
- 验证:`frontend/tests/rectification-agentic-entry.test.ts`;`npm run lint --prefix frontend -- src/components/rectification-agentic-chat.tsx`。
|
||||
- 防复发:盘面入口不得在 effect 里 `querySelector` 后同步 setState。源码合同锁定 callback ref 与 `headerSlot` prop,并禁止 chat 内 `setHeaderSlot`。
|
||||
- 相关记录:BUG-335、BUG-334
|
||||
- 复发自:BUG-335(把入口 portal 到页头时用了 effect 同步 setState)
|
||||
- 修复版本:f4619df2
|
||||
|
||||
## BUG-329 | 生时纠正 Agent 回答在结算后一次性出现,推理中无法停止
|
||||
|
||||
- 状态:resolved
|
||||
|
||||
@@ -1074,6 +1074,7 @@ export default function Home() {
|
||||
);
|
||||
const [rectificationSessionId, setRectificationSessionId] = useState<string | null>(null);
|
||||
const [rectificationCaseId, setRectificationCaseId] = useState<string | null>(null);
|
||||
const [rectificationHeaderSlot, setRectificationHeaderSlot] = useState<HTMLElement | null>(null);
|
||||
const [rectificationPendingQuestion, setRectificationPendingQuestion] = useState<string | null>(null);
|
||||
const [rectificationLoading, setRectificationLoading] = useState(false);
|
||||
const [rectificationMutationPending, setRectificationMutationPending] = useState(false);
|
||||
@@ -3566,7 +3567,7 @@ export default function Home() {
|
||||
<strong>{activeSession?.title || "新对话"}</strong>
|
||||
</div>
|
||||
<div className="chat-header-actions">
|
||||
<div className="chat-header-rectification" data-rectification-header-slot="" />
|
||||
<div className="chat-header-rectification" data-rectification-header-slot="" ref={setRectificationHeaderSlot} />
|
||||
<button className="credit-button" type="button" onClick={() => router.push(membershipHref("credits"))} aria-label={account ? `余额 ${account.credits} 点,会员与充值` : accountError || "读取余额中"}>
|
||||
<Sparkles className="credit-icon" aria-hidden="true" />
|
||||
<span>{account ? account.credits : "—"}</span>
|
||||
@@ -3806,6 +3807,7 @@ export default function Home() {
|
||||
onStartConsultation={() => void startConsultationAfterRectification()}
|
||||
pendingConsultationQuestion={rectificationPendingQuestion}
|
||||
onRestart={() => void startNewRectification()}
|
||||
headerSlot={rectificationHeaderSlot}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
onStartConsultation?: () => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
onRestart?: () => void;
|
||||
headerSlot: HTMLElement | null;
|
||||
}>;
|
||||
|
||||
export function ConversationalBirthTimeRectification(props: ConversationalBirthTimeRectificationProps) {
|
||||
|
||||
@@ -142,6 +142,7 @@ type RectificationAgenticChatProps = Readonly<{
|
||||
onStartConsultation?: () => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
onRestart?: () => void;
|
||||
headerSlot: HTMLElement | null;
|
||||
}>;
|
||||
|
||||
type RenderMessage = ChatMessageView & {
|
||||
@@ -240,6 +241,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onStartConsultation,
|
||||
pendingConsultationQuestion,
|
||||
onRestart,
|
||||
headerSlot,
|
||||
} = props;
|
||||
|
||||
const [messages, setMessages] = useState<RenderMessage[]>(() => messagesFromTurns(initialTurns));
|
||||
@@ -263,14 +265,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
const [compactBoard, setCompactBoard] = useState(false);
|
||||
const [boardOpen, setBoardOpen] = useState(false);
|
||||
const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null));
|
||||
const [headerSlot, setHeaderSlot] = useState<HTMLElement | null>(null);
|
||||
const boardId = useId();
|
||||
const boardTitleId = useId();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setHeaderSlot(document.querySelector("[data-rectification-header-slot]"));
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const query = window.matchMedia(`(max-width: ${RECTIFICATION_BOARD_SPLIT_MIN_PX - 1}px)`);
|
||||
const update = () => {
|
||||
|
||||
@@ -411,10 +411,13 @@ test("compact board overlays chat as a bottom sheet above the composer", () => {
|
||||
assert.match(styles, /\.rectification-board__close \{[\s\S]*min-height: 44px/);
|
||||
assert.match(board, /<X aria-hidden="true"/);
|
||||
assert.match(chat, /compactBoard && !boardOpen \? \(/);
|
||||
assert.match(chat, /createPortal\(/);
|
||||
assert.match(chat, /\[data-rectification-header-slot\]/);
|
||||
assert.match(chat, /headerSlot && boardPeek \? createPortal\(boardPeek, headerSlot\)/);
|
||||
assert.doesNotMatch(chat, /querySelector\(/);
|
||||
assert.doesNotMatch(chat, /setHeaderSlot/);
|
||||
assert.match(page, /className="chat-header-rectification"/);
|
||||
assert.match(page, /data-rectification-header-slot=""/);
|
||||
assert.match(page, /ref=\{setRectificationHeaderSlot\}/);
|
||||
assert.match(page, /headerSlot=\{rectificationHeaderSlot\}/);
|
||||
assert.match(styles, /\.chat-header-rectification \.rectification-board-peek \{[\s\S]*width: auto/);
|
||||
assert.doesNotMatch(chat, /rectification-workspace__board-trigger/);
|
||||
assert.doesNotMatch(chat.slice(chat.indexOf("className=\"composer-wrap\""), chat.indexOf("<form className=\"composer\"")), /RectificationBoardPeek/);
|
||||
|
||||
Reference in New Issue
Block a user