diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 20ada462..46d26772 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -4678,8 +4678,8 @@ - 根因:表来自 Case 快照、挂在消息列表末尾,却按聊天气泡的起始线排版,标题也只说“本命宫位”。 - 修复:宫位表单独放在消息列表后的 `rectification-snapshot`,表面与结果卡同类。标题改为“当前本命宫位”,并写明补充经历后会按新线索重算。时间选择卡不再进这个快照,见 BUG-312。 - 验证:`frontend/tests/rectification-agentic-entry.test.ts` 锁定快照容器在 `ChatMessageRow` 之外、不含候选卡、`aria-live`、重算文案,以及成功回合后 `loadCandidate`。 -- 防复发:宫位表必须在消息气泡外;文案必须说明随新线索重算。列对齐用 snapshot 容器的 `--assistant-content-inset`,不得再用表上的 `padding-inline-start` 把它扮成 Agent 正文。 -- 相关记录:BUG-307、BUG-310、BUG-312 +- 防复发:宫位表必须在消息气泡外,并作为聊天右侧工作台原地刷新,不得再挂在消息列表底部。文案必须说明随新线索重算。候选卡仍用 `--assistant-content-inset` 对齐 Agent 正文;宫位表不再用 inset 冒充气泡。 +- 相关记录:BUG-307、BUG-310、BUG-312、BUG-322 - 复发自:BUG-310(对齐 Agent 正文后更像一条聊天) - 修复版本:`4d872c68` @@ -4726,8 +4726,8 @@ - 根因:宫位表改成消息外的 snapshot 卡后,容器不再吃 `--assistant-content-inset`,仍按整列 `message-list` 铺宽;Agent 正文在头像加 gap 之后。 - 修复:snapshot 与候选卡同一列:`width: calc(100% - var(--assistant-content-inset))`,`margin-inline-start` 用同一 inset。表本身保持卡片描边和内边距,不用 `padding-inline-start` 冒充气泡。 - 验证:`frontend/tests/rectification-agentic-entry.test.ts` 锁定 snapshot 与候选卡共用 inset 宽度和起始线,且 `.rectification-house-table` 不写 inset。 -- 防复发:宫位表左缘和宽度必须跟 Agent 正文列一致;inset 只加在 snapshot 容器上。 -- 相关记录:BUG-310、BUG-311 +- 防复发:宫位表在右侧工作台内,不再与 Agent 正文同列对齐。候选时间卡仍用 `--assistant-content-inset` 对齐气泡。不得把工作台再缩进成消息列。 +- 相关记录:BUG-310、BUG-311、BUG-322 - 复发自:BUG-311(去掉消息缩进后未收回正文列) - 修复版本:`a732ff4b` @@ -4842,3 +4842,19 @@ - 相关记录:BUG-309、BUG-315、BUG-320 - 复发自:BUG-309(validate 跳过 `next build`;日志末尾 Import traces 掩盖类型检查失败) - 修复版本:0eebf971 + +## BUG-322 | 生时纠正宫位表和换升观察仍埋在消息列表底部 + +- 状态:resolved +- 首次发现:2026-08-20 +- 最近更新:2026-08-20 +- 影响面:生时纠正对话布局、`rectification-snapshot`、换升时刻 +- 用户现象:本命宫位和换升时刻跟在消息列表下面。每次补充经历后表会重算,但要滚过整段对话才能看见变化。 +- 触发条件:校正已有 Candidate Snapshot,继续在对话里补充经历。 +- 根因:BUG-311 把宫位表移出气泡,但仍放在 `.message-list` 末尾。换升时刻按图层一行一条平铺,不适合侧栏。 +- 修复:生时纠正单独做成聊天 | 工作台分栏。宫位表、按分钟合并的换升、确认门和大运对照放在右侧并原地刷新。候选时间卡和“用这个时间看盘”仍留在对话里。窄屏用“当前盘面”打开对话框,不挤第二列。普通咨询 session 不改。 +- 验证:`frontend/tests/rectification-agentic-entry.test.ts`;`frontend/tests/rectification-board-model.test.ts`。 +- 防复发:`rectification-snapshot` 不得回到消息循环。换升不得再按 `user_meaning` 一层一行平铺。`.conversation:not(.is-rectification)` 的 session 列宽不得套到校正对话。 +- 相关记录:BUG-311、BUG-312、BUG-314 +- 复发自:BUG-311(移出气泡后仍挂在消息列表底部) +- 修复版本:待提交 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index f3e2e7bd..1f027751 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -474,6 +474,7 @@ button:disabled { cursor: default; opacity: .45; } } .chat-panel { height: 100%; min-width: 0; min-height: 0; overflow: hidden; display: grid; background: var(--color-canvas); grid-template-rows: 68px minmax(0, 1fr) auto; } +.chat-panel.is-rectification { grid-template-rows: 68px minmax(0, 1fr); } .chat-header { z-index: 2; min-width: 0; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 20px; border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); padding: 0 var(--space-8); background: var(--color-frosted); backdrop-filter: saturate(130%) blur(20px); text-align: left; } .chat-header > div { min-width: 0; } .chat-header strong { max-width: min(560px, 62vw); overflow: hidden; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-display); font-size: var(--type-title-md); font-weight: 400; letter-spacing: -.3px; } @@ -1069,6 +1070,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .sidebar-scrim { position: fixed; z-index: 11; inset: 0; display: block; border: 0; background: var(--color-scrim); opacity: 1; visibility: visible; } [data-sidebar="rail"] { display: none; } .chat-panel { grid-template-rows: calc(64px + env(safe-area-inset-top)) minmax(0, 1fr) auto; } + .chat-panel.is-rectification { grid-template-rows: calc(64px + env(safe-area-inset-top)) minmax(0, 1fr); } .chat-header { gap: 10px; padding: max(0px, env(safe-area-inset-top)) var(--space-3) 0; } .conversation.is-empty { display: block; @@ -1122,6 +1124,17 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .paywall-footer > a, .paywall-footer > button { width: 100%; } .dialog-actions { flex-direction: column-reverse; } .dialog-actions > button { width: 100%; } + dialog.rectification-board { + width: 100%; + height: min(86dvh, 100%); + margin-block-start: auto; + margin-inline: 0; + border-inline-start: 0; + border-block-start: 1px solid var(--color-border); + border-start-start-radius: var(--radius-lg); + border-start-end-radius: var(--radius-lg); + padding-bottom: env(safe-area-inset-bottom); + } .auth-story { min-height: 0; padding: var(--space-5); } .auth-story h2 { font-size: var(--type-title-lg); } .auth-panel { padding: var(--space-6) var(--space-5) max(var(--space-6), env(safe-area-inset-bottom)); } @@ -1847,12 +1860,218 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class .birth-time-clock-menu.select-content { width: 108px; min-width: 108px; } .birth-time-clock-menu .select-item { justify-content: flex-start; } +.rectification-workspace { + min-width: 0; + min-height: 0; + height: 100%; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(18rem, 22.5rem); + background: var(--color-canvas); +} +.rectification-workspace.is-compact { + grid-template-columns: minmax(0, 1fr); +} +.rectification-workspace__chat { + min-width: 0; + min-height: 0; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; +} +.rectification-workspace__chat .conversation { + padding-bottom: var(--space-4); +} +.rectification-workspace__chat .message-list { + width: 100%; + max-width: 720px; +} +.rectification-workspace__chat .composer-wrap { + position: relative; + z-index: 2; +} +.rectification-board { + min-width: 0; + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + border-inline-start: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); + background: var(--color-canvas-soft); + color: var(--color-ink); +} +dialog.rectification-board { + width: min(22.5rem, 100%); + height: 100dvh; + max-width: none; + max-height: none; + margin: 0; + margin-inline-start: auto; + padding: 0; + border: 0; + border-inline-start: 1px solid var(--color-border); + background: var(--color-canvas-soft); + color: var(--color-ink); +} +dialog.rectification-board::backdrop { + background: var(--color-scrim); +} +.rectification-board__header { + position: sticky; + top: 0; + z-index: 1; + display: grid; + gap: var(--space-2); + padding: var(--space-4) var(--space-4) var(--space-3); + background: var(--color-canvas-soft); +} +.rectification-board__title-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-2) var(--space-3); +} +.rectification-board__header h2 { + margin: 0; + font-family: var(--font-display); + font-size: var(--type-title-sm); + font-weight: 600; + letter-spacing: -.2px; +} +.rectification-board__clock { + color: var(--color-ink); + font-variant-numeric: tabular-nums; + font-size: var(--type-body-sm); + font-weight: 600; +} +.rectification-board__close { + min-height: 44px; + margin-inline-start: auto; + padding: 0 var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-canvas); + color: var(--color-ink); + cursor: pointer; +} +.rectification-board__close:focus-visible, +.rectification-board-peek:focus-visible { + outline: 2px solid var(--color-focus); + outline-offset: 2px; +} +.rectification-board__stage, +.rectification-board__empty, +.rectification-board__disclosure { + margin: 0; + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); + line-height: 1.55; +} +.rectification-board__body { + min-height: 0; + display: grid; + align-content: start; + gap: var(--space-6); + overflow-y: auto; + overscroll-behavior: contain; + padding: 0 var(--space-4) var(--space-6); +} +.rectification-board .rectification-snapshot { + display: grid; + gap: var(--space-4); + width: auto; + margin: 0; +} +.rectification-board .rectification-house-table { + padding: 0; + border: 0; + border-radius: 0; + background: transparent; +} +.rectification-board .technique-audit { + margin-top: 0; + padding-top: 0; + border-top: 0; +} +.rectification-board__section { + display: grid; + gap: var(--space-3); +} +.rectification-board__section h3 { + margin: 0; + font-family: var(--font-display); + font-size: var(--type-title-sm); + font-weight: 600; + letter-spacing: -.2px; +} +.rectification-board__minutes { + display: grid; + gap: var(--space-3); + margin: 0; + padding: 0; + list-style: none; +} +.rectification-board__minute { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 6px 10px; +} +.rectification-board__minute time { + font-variant-numeric: tabular-nums; + font-weight: 600; +} +.rectification-board__minute-note { + color: var(--color-ink-secondary); + font-size: var(--type-caption); +} +.rectification-board__chips { + display: flex; + flex-wrap: wrap; + gap: 6px; + flex: 1 1 100%; + margin: 0; + padding: 0; + list-style: none; +} +.rectification-board__chip { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 8px; + border-radius: 999px; + background: var(--color-canvas); + color: var(--color-ink); + font-size: var(--type-caption); +} +.rectification-board-peek { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + width: 100%; + min-height: 44px; + margin: 0 0 var(--space-2); + padding: var(--space-2) var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-canvas-soft); + color: var(--color-ink); + text-align: start; + cursor: pointer; +} +.rectification-board-peek strong { font-size: var(--type-body-sm); } +.rectification-board-peek span { + color: var(--color-ink-secondary); + font-size: var(--type-caption); +} +.rectification-house-table__changed { + display: inline; + margin-inline-start: .4em; + color: var(--color-ink-secondary); + font-size: var(--type-caption); +} + .rectification-snapshot { display: grid; gap: var(--space-3); - width: calc(100% - var(--assistant-content-inset)); - margin: 8px 0 16px; - margin-inline-start: var(--assistant-content-inset); } .rectification-candidates { display: grid; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8c6cd2f5..ae7792f4 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -3552,7 +3552,7 @@ export default function Home() { ) : null} - +
diff --git a/frontend/src/components/rectification-agentic-chat.tsx b/frontend/src/components/rectification-agentic-chat.tsx index cb4df9bb..10717cf0 100644 --- a/frontend/src/components/rectification-agentic-chat.tsx +++ b/frontend/src/components/rectification-agentic-chat.tsx @@ -1,7 +1,7 @@ "use client"; import { ArrowUp } from "lucide-react"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react"; import { parseAgentReply } from "@/lib/agent-reply"; import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view"; import { @@ -17,6 +17,10 @@ import { workingRectificationHouseTable, type RectificationCandidateResult, } from "@/lib/rectification-candidate-result"; +import { + diffRectificationBoard, + RECTIFICATION_BOARD_SPLIT_MIN_PX, +} from "@/lib/rectification-board-model"; import { membershipHref } from "@/lib/membership"; import { vargaSentenceFromMethods } from "@/lib/rectification-varga-sentence"; import { @@ -30,8 +34,7 @@ import { toggleChatMessageFeedback, } from "./chat-message-actions"; import { ModelSelector } from "./model-selector"; -import { RectificationHouseTableView } from "./rectification-house-table"; -import { TechniqueAuditDisclosure, ConfirmationGateDisclosure } from "./technique-audit-disclosure"; +import { RectificationBoard, RectificationBoardPeek } from "./rectification-board"; import { Button } from "./ui/button"; import { Textarea } from "./ui/textarea"; @@ -104,75 +107,6 @@ function RectificationCandidateCards({ ); } -function RectificationRefinementPanel({ - result, -}: Readonly<{ result: RectificationCandidateResult }>) { - const transitions = result.windowTransitions; - const ledger = result.eventDashaLedger; - const agreement = result.dashaAgreement; - const contrast = result.lagnaContrast; - const nakshatra = result.nakshatraBoundary; - const stage = result.precisionStage; - if ( - transitions.length === 0 - && ledger.length === 0 - && !agreement - && !contrast - && !nakshatra?.near_boundary - && !stage - ) return null; - return ( -
- {stage && ( -

{stage.user_meaning}

- )} - {transitions.length > 0 && ( -
- 换升时刻 -
    - {transitions.map((item) => ( -
  • {item.user_meaning}
  • - ))} -
-
- )} - {contrast && ( -
- 两段本命上升 -

{contrast.user_meaning}

-
    - {contrast.intervals.map((interval) => ( -
  • - {interval.start}-{interval.end}:{interval.lagna} - {interval.lords.l1 ? `;一/四/七/十宫主 ${[interval.lords.l1, interval.lords.l4, interval.lords.l7, interval.lords.l10].filter(Boolean).join("、")}` : ""} -
  • - ))} -
-
- )} - {ledger.length > 0 && ( -
- 经历与大运对照 -
    - {ledger.map((item) => ( -
  • {item.user_meaning}
  • - ))} -
-
- )} - {agreement && ( -

{agreement.user_meaning}

- )} - {nakshatra?.near_boundary && nakshatra.user_meaning && ( -
- 交界节奏 -

{nakshatra.user_meaning}

-
- )} -
- ); -} - function RectificationOosPanel({ prompts, }: Readonly<{ prompts: RectificationCandidateResult["oosBlindPrompts"] }>) { @@ -319,9 +253,37 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { const [copiedMessageKey, setCopiedMessageKey] = useState(null); const [regeneratingMessageKey, setRegeneratingMessageKey] = useState(null); const conversation = useRef(null); + const workspace = useRef(null); const composer = useRef(null); const keyCounter = useRef(0); const openingStarted = useRef(false); + const previousBoardResult = useRef(null); + const [compactBoard, setCompactBoard] = useState(false); + const [boardOpen, setBoardOpen] = useState(false); + const [boardDiff, setBoardDiff] = useState(() => diffRectificationBoard(null, null)); + const boardId = useId(); + const boardTitleId = useId(); + + useLayoutEffect(() => { + const node = workspace.current; + if (!node) return; + const update = () => { + const nextCompact = node.clientWidth < RECTIFICATION_BOARD_SPLIT_MIN_PX; + setCompactBoard(nextCompact); + if (!nextCompact) setBoardOpen(false); + }; + update(); + const observer = new ResizeObserver(update); + observer.observe(node); + return () => observer.disconnect(); + }, []); + + useEffect(() => { + setBoardDiff(diffRectificationBoard(previousBoardResult.current, candidateResult)); + previousBoardResult.current = candidateResult; + }, [candidateResult]); + + const closeBoard = useCallback(() => setBoardOpen(false), []); const setPending = useCallback((value: boolean) => { setBusy(value); @@ -684,9 +646,13 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { const canSend = !busy && !readonly && !regeneratingMessageKey; return ( - <> -
-
+
+
+
+
{pendingConsultationQuestion?.trim() && (

先陪你核对出生时间范围,之后会回到你原来的问题:“{pendingConsultationQuestion.trim()}” @@ -746,19 +712,6 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {

); })} - {candidateResult?.houseTable && ( -
- {candidateResult.natalRecast && ( -

{candidateResult.natalRecast.user_meaning}

- )} - - - {savedStatus === "accepted" && candidateResult.techniqueAudit.length > 0 && ( - - )} -
- )} - {candidateResult && } {savedTime && (

@@ -786,8 +739,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {

-
-
+
+ {compactBoard && ( + setBoardOpen(true)} + /> + )} +