diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b87f40..7ad2e7cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # 印度占星 Skill 更新日志 +## 2026-10-28 — 生时校正的结果卡和盘面 + +- 结果分得出先后时,卡片最上面直接写最可能的出生时间和上升,按钮「采用 这个时间 并打开星盘」,采用后直接去星盘页;还不能排除的时间留在下面,仍然可以改用。 +- 卡上可以点「我说错了,改一条」:选一条记下的事,输入框里先写好开头,补完发出就会改掉那一条。 +- 代表时刻只在对话上方的时间轴写一次;当前盘面改成点「看盘面」再打开。 +- Skill 版本不变(issue #146、#147)。 + ## 2026-10-28 — 两处分盘敏感点按来源修正 - Sarpa Drekkana 从三段补成书上的五段:巨蟹第 3 段、天蝎第 2 段以前漏掉,现在会标出来;正好落在 10 度边界上的盘不再划到上一段。 diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 829bdd45..5dcc0ccb 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -918,6 +918,8 @@ Admin 的 antd `` 是独立设计系统,不在此表。 **「已对照 N 件」在手机上不在可见行里,桌面上目前仍在——这是一处未裁决的口径冲突。** 下面的禁列表写明这一项归交付卡、不上条,而它后来(cfb41daf)被加到了条上且没有改本文件。BUG-918 只把它在手机上移出可见行;桌面是否也撤掉需要产品裁决,见 `docs/BUG_HISTORY.md` BUG-918。 +**代表分钟只写在条上(2026-10-28,issue #146)。** 盘面标题行不再写时刻,「看盘面」按钮只写这三个字(读屏名称仍带时刻和上升)。下面 D10 那段里「条上与盘面标题行同一次调用」从此只剩条这一处。 + **代表分钟上条是 D10(2026-09-16)的产品决定,推翻了「条上只有区间」的原设计。** 理由是窄屏:`.is-compact` 下右栏盘面整体变成默认关闭的 overlay,代表分钟此前只在 `.rectification-board__clock` 出现,于是用户最该一直看着的那个数字恰恰看不见。条上的取值必须与盘面标题行**同一次调用**——`workingRectificationTime(result)`,由 `rectification-chat-view.ts` 的 `deriveRectificationChatView` 算一次(容器 `rectification-agentic-chat.tsx` 每次渲染调用它一次;2026-09-26 代码拆分只搬位置)、两处共用,不允许第二条推导路径。已采用时写「已采用 HH:MM」,否则写「代表分钟 HH:MM」;**采用不是确认**,完整边界句仍只在交付 / 采用旁白里出现一次,条上不写、也不得暗示某一分钟已确认。 **条上不得出现收窄进度(「已从 30 分钟收到 7 分钟」)。** 开场窗口确实在线上,但没有任何投影把这个对比作为字段给出,而 VOICE.md 第 2 条要求进度数字来自服务端。前端自己减还会算错:轴是**当前**窗口且会放宽(BUG-572),相减会把一次放宽报成进度。这句话继续由服务端拼进旁白。缺的字段见 `BLOCKED.md`。 @@ -955,6 +957,14 @@ Admin 的 antd `` 是独立设计系统,不在此表。 ## 11. 区间交付卡 +**卡头与按钮(2026-10-28,issue #147,产品同意 https://git.copse.top/root/Jyotisha/issues/147#issuecomment-36056 四条)。** 下文 D3、D4 与「更像这个」按以下改: + +- 区分得开(D4:前两列差 ≥ 5 个百分点)且有代表分钟时,卡头是 `.rectification-result-lead`:小字「最可能的出生时间」、30px 衬线时刻、「上升X」(有该分钟的宫位表才写);「目前范围 …(对照了 N 件经历)」降为下一行 `.rectification-range-delivery__range-line`。区分不开时 D3 原样:范围是 `` 卡头,加「区分不开」那句,不写最可能。 +- 区分得开时最可能那列排第一,按钮是实心「采用 HH:MM 并打开星盘」,采用成功(服务端已保存)后去 `/chart`;其余列在小字「还不能排除」下面,按钮是描边「改用这个」,采用后不跳转。区分不开时每列仍是「更像这个」。出生记录冲突时一律「改用校正结果」,不跳转。 +- 不显示把握(低 / 中 / 高):引擎的这一档没有校准,BUG-560 同理。 +- 可以采用时,卡上多一个文字按钮「我说错了,改一条」,展开用户确认过的事件(与对话里的复述同一说法),点一条只在输入框里写好「我说错了:「…」应该是」,由用户补完发出,现有改记录工具来改;不自动发送、不另扣点。没有确认过的事件就不出这个按钮。不放「再补一件事」:训练门后补打字经历几乎缩不了范围,卡上不得邀请再补(BUG-1084,issue #148 产品 2026-10-28 定)。 +- 盘型交付卡(「用哪张盘解读」)不加时刻卡头(BUG-1151:盘型产品不排分钟),按钮文案不变;平常采用后同样去 `/chart`,也有上面那一行。 + 交付对象是区间里并排的候选分钟,不是支持度数字,也不是逐行空差异。 **相同的句子只写一次。** 两列或三列共有的 D9 / D10 / 月宿性格句提到卡片顶部(「三个时间的事业盘都在巨蟹座:做事以照顾人为主」)。列内只留有差别的句子;某列没有差别句就不显示性格块,不写「同上」。单列不提升共享句。 @@ -973,6 +983,8 @@ Admin 的 antd `` 是独立设计系统,不在此表。 ## 12. 校正右栏盘面 +**默认关着(2026-10-28,issue #146)。** 所有宽度上,头部都有「看盘面」按钮(`.rectification-board-peek`),盘面默认不出。桌面点开后在右侧分出一栏(`minmax(18rem, 22.5rem)`,没有候选时 16–18rem),280ms 从右淡入,标题行右侧是关闭按钮;手机仍是底部抽屉。标题行不写时刻。 + 右栏回答「当前按哪一分钟在看盘」,不回答「选哪一分钟」。顶部不加任何新说明文字。 **本命宫位改北印星盘。** 用 `VedicChartSvg` 渲染代表分钟的 `house_table`。宫位表退到星盘下方的 `
`(读屏与复制仍可用)。换分钟时只高亮变化的宫(`changedHouses` / `lagnaChanged`),不做动画。星盘组件在校正面 `dynamic()` 分包里,不进首页首屏。 diff --git a/frontend/docs/VOICE.md b/frontend/docs/VOICE.md index c89103e1..a40c256f 100644 --- a/frontend/docs/VOICE.md +++ b/frontend/docs/VOICE.md @@ -63,6 +63,12 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 报告末尾「对哪一段想再问清楚?」列出前八节(含关系时间线,不含共同练习)。点进去是一条新对话,输入框预先写好这句,用户自己发送。中文预填是「合盘报告里「节名」这一段,在我们两人的盘里依据是什么?」英文报告用英文问同一件事。对话里可以说星曜、宫位、九分盘、大运,但只能引用这份报告已经写下的计算。材料里没有的,说「这份合盘没算到」。对不上的盘面,说「这一点在这份合盘里没有算到,我不能确定」。报告删了之后,输入框写「这份合盘已删除」,不能再发送。英文还没写好时只说「英文版即将提供」。选人页不写副标题。按钮是「开始合盘」,上面不写价格。点了先确认「会用掉 N 点,余额 M 点。」,按钮是「取消 / 开始」。余额不够时说「需要 N 点,余额只有 M 点。」,主按钮是「去补点数」。N 从计价表读,M 从账户读。计价读不到时不弹确认,只说「计费配置不可用」。这两人已经有写好的合盘、且没有一份正在生成时,按钮下面加「这两人已经合过 N 次,再生成会另扣 X 点」,N、X 都读实际数字。正在生成时不写这句。下拉占位是「选择一个人」。资料不齐写「资料未齐」,已经选在另一边写「已在另一边」。档案满了写「档案已满(最多 20 人)」,旁边是「去星盘档案删一个」。新增星盘卡片用「称呼」「性别」「出生日期」「出生时间」「出生地」,按钮是「取消」和「保存并选中」。产品关着时说「合盘服务暂未开放」。开关读不到时说「合盘服务暂时连不上,请稍后再试」。开关结果回来之前,选人页不出现两个人的选择框,也不出现「开始合盘」。英文次数用尽时说「英文版暂时无法再试」。生成中说「正在写 甲 × 乙 的合盘报告…」,四步是「算两张完整命盘」「整理两人之间的证据」「写报告」「检查报告」,下面是「大约要几分钟。可以先离开,写好后在「之前的合盘」和「报告」里都能找到。」英文等待说「正在写英文版…」和「第一次切到英文时才写,不扣点,大约一两分钟。」报告标题下是「已保存,再次打开不扣点」。 +## 生时校正界面:收束卡与盘面(2026-10-28,issue #146、#147) + +- 头部按钮「看盘面」;盘面标题「当前盘面」,不写时刻。 +- 收束卡区分得开时:「最可能的出生时间」+ 时刻 + 「上升X」,下一行仍是「目前范围 …(对照了 N 件经历)」。最可能那列按钮「采用 HH:MM 并打开星盘」;其余列上方小字「还不能排除」,按钮「改用这个」。区分不开时不变(「更像这个」、「这几个时刻按现在的方法区分不开。」)。不写「把握」。 +- 卡上一个文字按钮「我说错了,改一条」(不放「再补一件事」,BUG-1084);展开后一句「选一条要改的,输入框里会先写好开头:」,点一条后输入框写「我说错了:「事件」应该是」。 + ## 生时校正独立入口(2026-10-28,issue #145) 产品 2026-10-28 定:校正有自己的入口,不再从对话里开案例。下面这节「生时校正从对话进入」里的卡片文案和「明确要求则直接进入」由本节取代,其余(何时提议、做不了必须说一句、正文怎么提、回来)不变。 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 30894b2b..baa57379 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -2827,14 +2827,26 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class min-height: 0; height: 100%; display: grid; - grid-template-columns: minmax(0, 1fr) minmax(18rem, 22.5rem); + grid-template-columns: minmax(0, 1fr); background: var(--color-canvas); } +/* Issue #146: the board is closed by default; 「看盘面」 in the header opens it + as a right column on wide screens. */ +.rectification-workspace.is-board-open:not(.is-compact) { + grid-template-columns: minmax(0, 1fr) minmax(18rem, 22.5rem); +} /* No candidate yet: the board only carries the declared time, so it takes less width until there is a house table to show. */ -.rectification-workspace.is-board-empty { +.rectification-workspace.is-board-empty.is-board-open:not(.is-compact) { grid-template-columns: minmax(0, 1fr) minmax(16rem, 18rem); } +.rectification-workspace:not(.is-compact) .rectification-board { + animation: rectification-board-enter 280ms cubic-bezier(.22, 1, .36, 1) both; +} +@keyframes rectification-board-enter { + from { opacity: 0; transform: translateX(16px); } + to { opacity: 1; transform: none; } +} .rectification-workspace.is-compact { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); @@ -7201,3 +7213,83 @@ button.synastry-dd-add { border-radius: 0 0 var(--radius-md) var(--radius-md); } .starter-rectify-link:hover { background: var(--color-canvas-muted); } + +/* Result card (issue #147). The most likely minute leads only when the card + says the columns can be told apart (D4); the range drops to the line under + it. Candidates that cannot be ruled out keep their own adopt button. */ +.rectification-result-lead { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--space-1) var(--space-3); +} +.rectification-result-lead__label { + color: var(--color-ink-tertiary); + font-size: var(--type-body-sm); +} +.rectification-result-lead__time { + color: var(--color-ink); + font-family: var(--font-display); + font-size: 30px; + font-weight: 500; + line-height: 1.2; + font-variant-numeric: tabular-nums; +} +.rectification-result-lead__lagna { + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); +} +.rectification-range-delivery__range-line { + margin: 0; + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); + font-variant-numeric: tabular-nums; +} +.rectification-range-delivery__others { + margin: 0 0 var(--space-2); + padding-top: var(--space-3); + border-top: 1px solid var(--color-canvas-muted); + color: var(--color-ink-tertiary); + font-size: var(--type-caption); +} +.rectification-result-actions { + display: grid; + gap: var(--space-2); +} +.rectification-result-actions__row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-2); +} +.rectification-result-actions__fix { + color: var(--color-action); + text-decoration: underline; + text-underline-offset: 3px; +} +.rectification-result-actions__list { + display: grid; + gap: var(--space-2); + padding: var(--space-3); + border-radius: var(--radius-lg); + background: var(--color-canvas-soft); + animation: rectification-result-list-enter 260ms cubic-bezier(.22, 1, .36, 1) both; +} +.rectification-result-actions__list p { + margin: 0; + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); +} +.rectification-result-actions__items { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} +.rectification-result-actions__items button { + white-space: normal; + text-align: start; +} +@keyframes rectification-result-list-enter { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: none; } +} diff --git a/frontend/src/components/rectification-agentic-chat.tsx b/frontend/src/components/rectification-agentic-chat.tsx index 74d7c619..1e1a9a86 100644 --- a/frontend/src/components/rectification-agentic-chat.tsx +++ b/frontend/src/components/rectification-agentic-chat.tsx @@ -7,6 +7,8 @@ import { createPortal } from "react-dom"; import type { ChatMessage } from "@/lib/chat-message-view"; import { canShowRectificationSelectionCards } from "@/lib/rectification-candidate-result"; import { RectificationRangeDelivery } from "@/components/rectification-range-delivery"; +import { useOpenChartAfterAdopt } from "@/hooks/use-open-chart-after-adopt"; +import { fixEvidenceDraft } from "@/lib/rectification-fix-evidence"; import { livingDisplayFromReceipt } from "@/lib/rectification-agentic/core/candidate-window"; import { RECTIFICATION_EMPTY_ACTION_LABEL, @@ -170,7 +172,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { currentQuestionRef, applyCaseSnapshot, loadCaseSnapshot, + fixableEvidence, } = useRectificationCaseSnapshot({ caseId, sessionId, initialSnapshot }); + const { arm: armChartAfterAdopt, onSaved: savedThenChart } = useOpenChartAfterAdopt(onSaved); const readonly = historyReadonly || Boolean(resultNotice); const conversationReadonly = historyReadonly || (Boolean(resultNotice) && !canRecompare); const [messages, setMessages] = useState(() => messagesFromTurns(initialTurns)); @@ -365,30 +369,33 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { void send("opening", ""); }, [initialTurns.length, onOpeningConsumed, readonly, send, shouldStartOpening]); - const acceptCandidate = useCallback(async (candidateId: string) => runRectificationCandidateAccept({ - caseId, - sessionId, - busy, - readonly, - candidateResult, - acceptingCandidateId, - keyCounter, - runAbort, - lastRunOutcome, - conversationAnchor, - setError, - setMessages, - setAcceptingCandidateId, - setCandidateResult, - setSavedTime, - setSavedStatus, - setPending, - beginLiveRun, - loadCaseSnapshot, - send, - onCompleted, - onSaved, - }, candidateId), [acceptingCandidateId, beginLiveRun, busy, candidateResult, caseId, loadCaseSnapshot, onCompleted, onSaved, readonly, send, sessionId, setPending]); + const acceptCandidate = useCallback(async (candidateId: string, openChart = false) => { + armChartAfterAdopt(openChart); + return runRectificationCandidateAccept({ + caseId, + sessionId, + busy, + readonly, + candidateResult, + acceptingCandidateId, + keyCounter, + runAbort, + lastRunOutcome, + conversationAnchor, + setError, + setMessages, + setAcceptingCandidateId, + setCandidateResult, + setSavedTime, + setSavedStatus, + setPending, + beginLiveRun, + loadCaseSnapshot, + send, + onCompleted, + onSaved: savedThenChart, + }, candidateId); + }, [acceptingCandidateId, armChartAfterAdopt, savedThenChart, beginLiveRun, busy, candidateResult, caseId, loadCaseSnapshot, onCompleted, readonly, send, sessionId, setPending]); async function copyMessage(message: RenderMessage) { await copyRectificationMessage({ setCopiedMessageKey }, message); @@ -533,7 +540,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) { }; }); - const boardPeek = compactBoard && !boardOpen ? ( + // Issue #146: 「看盘面」 is in the header at every width. On a phone the open + // sheet covers it; on a desktop it stays as the toggle beside the open board. + const boardPeek = !compactBoard || !boardOpen ? ( void acceptCandidate(candidateId)} + onAccept={(candidateId, openChart) => void acceptCandidate(candidateId, openChart)} adoptLocked={liveInterviewBlockingAdopt} + fixableEvidence={fixableEvidence} + onFixEvidence={(line) => { + setDraft(fixEvidenceDraft(line)); + composer.current?.focus({ preventScroll: true }); + }} /> {verifiedIdleCopy && (

diff --git a/frontend/src/components/rectification-board.tsx b/frontend/src/components/rectification-board.tsx index 16ffc739..a2cddced 100644 --- a/frontend/src/components/rectification-board.tsx +++ b/frontend/src/components/rectification-board.tsx @@ -94,12 +94,9 @@ function RectificationBoardBody({

当前盘面

- {workingTime - ? {workingTime} - : emptyCopy.clock - ? {emptyCopy.clock} - : null} - {compact && ( + {/* Issue #146: the working minute is written once, on the timeline + above the conversation. The board no longer repeats it. */} + {( ); } diff --git a/frontend/src/components/rectification-range-delivery.tsx b/frontend/src/components/rectification-range-delivery.tsx index bdace93a..fa3f4709 100644 --- a/frontend/src/components/rectification-range-delivery.tsx +++ b/frontend/src/components/rectification-range-delivery.tsx @@ -18,6 +18,12 @@ import { RectificationVerificationReport } from "@/components/rectification-veri import { RectificationSegmentDelivery } from "@/components/rectification-segment-delivery"; import { sessionOutcomeAllowsAdopt } from "@/lib/rectification-agentic/core/rectification-decision"; import { Button } from "@/components/ui/button"; +import { RectificationResultActions, RectificationResultLead } from "@/components/rectification-result-parts"; +import { + RECTIFICATION_OTHER_CANDIDATES_LABEL, + RECTIFICATION_SWITCH_TO_THIS_LABEL, + adoptAndOpenChartLabel, +} from "@/lib/rectification-fix-evidence"; import { deliveryWindowFromReceipt, protectVargaReport } from "@/lib/rectification-agentic/v9/delivery-window"; @@ -31,12 +37,17 @@ export function RectificationRangeDelivery({ readonly, onAccept, adoptLocked, + fixableEvidence = [], + onFixEvidence, }: Readonly<{ result: RectificationCandidateResult; acceptingCandidateId: string | null; readonly: boolean; - onAccept: (candidateId: string) => void; + /** `openChart`: the most likely column's 「采用 HH:MM 并打开星盘」 (issue #147). */ + onAccept: (candidateId: string, openChart?: boolean) => void; adoptLocked?: boolean; + fixableEvidence?: readonly string[]; + onFixEvidence?: (line: string) => void; }>) { const delivery: RangeDeliveryProjection | null = result.rangeDelivery; const range = delivery?.range ?? result.credibleRange; @@ -71,24 +82,48 @@ export function RectificationRangeDelivery({ // D4: per-column numbers only when the first column leads by ≥ 5 points. const showPercents = rangeDeliveryShowsPercents(columns); const indistinct = columns.length >= 2 && !showPercents; + // Issue #147: lead with the most likely minute only when D4 says the columns + // can be told apart; otherwise the range stays the headline (D3). + const primaryColumn = mostLikely && !indistinct + ? columns.find((column) => column.candidate_id === delivery?.representative_candidate_id) + ?? columns.find((column) => column.time === mostLikely) + ?? null + : null; + const primaryLagna = primaryColumn + ? result.houseTablesByTime[primaryColumn.time]?.lagna + ?? (result.houseTable?.time === primaryColumn.time ? result.houseTable.lagna : null) + ?? null + : null; + const orderedColumns = primaryColumn + ? [primaryColumn, ...columns.filter((column) => column !== primaryColumn)] + : columns; + const showActions = adoptAllowed && !adoptLocked && !readonly; if (delivery && (delivery.product_delivery || delivery.segment_summary)) { return candidate.candidateId === delivery.representative_candidate_id)?.candidate_date} />; + candidateDate={result.candidates.find((candidate) => candidate.candidateId === delivery.representative_candidate_id)?.candidate_date} + fixableEvidence={fixableEvidence} onFixEvidence={onFixEvidence} readonly={readonly} />; } return (
-
- {title} - {mostLikely && !indistinct ? ( - - {rangeDeliveryMostLikely(mostLikely)} - - ) : null} -
+ {primaryColumn ? ( + <> + +

{title}

+ + ) : ( +
+ {title} + {mostLikely && !indistinct ? ( + + {rangeDeliveryMostLikely(mostLikely)} + + ) : null} +
+ )} {vargaBlocked ? (

{windowMinutes === null ? "完整窗口范围暂不可用;暂不推荐分盘解读。" : "分盘解读需要更准的出生时间"}

) : caption ? ( @@ -118,7 +153,8 @@ export function RectificationRangeDelivery({ ) : null}
    - {columns.map((column) => { + {orderedColumns.map((column, index) => { + const isPrimary = column === primaryColumn; const candidateDate = result.candidates.find((candidate) => candidate.candidateId === column.candidate_id)?.candidate_date; const relativeDay = candidateDate && result.declaredBirthDate && candidateDate !== result.declaredBirthDate ? candidateDate < result.declaredBirthDate ? "(前一天)" : "(后一天)" : ""; @@ -128,8 +164,11 @@ export function RectificationRangeDelivery({ return (
  • + {primaryColumn && index === 1 ? ( +

    {RECTIFICATION_OTHER_CANDIDATES_LABEL}

    + ) : null}
    {candidateDate ? `${candidateDate}${relativeDay} ${column.time}` : column.time}
    {adoptAllowed && !adoptLocked && candidateDate ?

    采用后,排盘使用这一日期和时间;原填报日期保留。

    : null} {showPercents ? ( @@ -162,11 +201,11 @@ export function RectificationRangeDelivery({ <> {recordConflict && !adopted ? (

    @@ -189,6 +232,13 @@ export function RectificationRangeDelivery({ ); })}

+ {showActions ? ( + + ) : null} {delivery?.more_label ? (

{delivery.more_label}

) : null} diff --git a/frontend/src/components/rectification-result-parts.tsx b/frontend/src/components/rectification-result-parts.tsx new file mode 100644 index 00000000..9120dff2 --- /dev/null +++ b/frontend/src/components/rectification-result-parts.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useId, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { RECTIFICATION_FIX_COPY, RECTIFICATION_MOST_LIKELY_LABEL } from "@/lib/rectification-fix-evidence"; + +/** + * Issue #147: the result card leads with the most likely minute when the + * server says the candidates can be told apart (D4). The card decides that; + * this only draws it. + */ +export function RectificationResultLead({ time, lagna }: Readonly<{ time: string; lagna: string | null }>) { + return ( +
+ {RECTIFICATION_MOST_LIKELY_LABEL} + {time} + {lagna ? 上升{lagna} : null} +
+ ); +} + +/** + * 「我说错了,改一条」 lists the confirmed events and fills the composer with + * the chosen one. Nothing is sent here. There is deliberately no 「再补一件事」: + * typed events barely narrow the range after the training gate, so the card + * must not invite more (BUG-1084, issue #148 decision 2026-10-28). + */ +export function RectificationResultActions({ + evidence, + disabled, + onFixEvidence, +}: Readonly<{ + evidence: readonly string[]; + disabled: boolean; + onFixEvidence?: (line: string) => void; +}>) { + const [fixOpen, setFixOpen] = useState(false); + const listId = useId(); + if (!onFixEvidence || evidence.length === 0) return null; + return ( +
+
+ +
+ {fixOpen ? ( +
+

{RECTIFICATION_FIX_COPY.pickHint}

+
+ {evidence.map((line) => ( + + ))} +
+
+ ) : null} +
+ ); +} diff --git a/frontend/src/components/rectification-segment-delivery.tsx b/frontend/src/components/rectification-segment-delivery.tsx index 42be37a7..268c57d9 100644 --- a/frontend/src/components/rectification-segment-delivery.tsx +++ b/frontend/src/components/rectification-segment-delivery.tsx @@ -8,8 +8,10 @@ import { RectificationVerificationReport } from "@/components/rectification-veri import { protectVargaReport } from "@/lib/rectification-agentic/v9/delivery-window"; import { unknownProductDeliveryCopy } from "@/lib/rectification-agentic/v9/product-delivery"; import { Button } from "@/components/ui/button"; +import { RectificationResultActions } from "@/components/rectification-result-parts"; -export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, selection, resultId, busy, accepting, onAccept, adoptLocked, markdown, candidateDate, canAdopt = false }: Readonly<{ +export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, selection, resultId, busy, accepting, onAccept, adoptLocked, markdown, candidateDate, canAdopt = false, + fixableEvidence = [], onFixEvidence, readonly = false }: Readonly<{ summary: SegmentSummary | null; delivery: RangeDeliveryProjection; rangeLabel: string | null; @@ -17,11 +19,15 @@ export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, se resultId: string; busy: boolean; accepting: boolean; - onAccept: (candidateId: string) => void; + /** `openChart`: 「采用 HH:MM 并打开星盘」 (issue #147). */ + onAccept: (candidateId: string, openChart?: boolean) => void; adoptLocked?: boolean; markdown?: string | null; candidateDate?: string; canAdopt?: boolean; + fixableEvidence?: readonly string[]; + onFixEvidence?: (line: string) => void; + readonly?: boolean; }>) { const adoption = summary?.adoption_minute; const candidateId = delivery.representative_candidate_id; @@ -36,6 +42,10 @@ export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, se const blocked = summary?.charts.some((chart) => chart.tier === "blocked") === true; const reportBlocked = !summary || blocked; const showAdopt = Boolean(canAdopt && adoption && candidateId && !adoptLocked); + // Issue #147: no minute lead here. The segment product answers "which chart", + // never "which minute" (BUG-1151, product 10-01 option A). A plain adopt still + // opens the chart afterwards; re-adopt and record-conflict adopts do not. + const plainAdopt = !(readopt && priorAdoption) && !delivery.record_conflict; return (
用哪张盘解读
@@ -58,8 +68,8 @@ export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, se {showAdopt && adoption && candidateId ? ( <>

采用后按 {adoption.date ? `${adoption.date} ` : ""}{adoption.time} 排盘;原填报资料保留。

-