fix(rectification): cover all candidate minutes on the compare card and keep delivery narration (BUG-614/615)
Independent Staging Quality Gate / validate (push) Failing after 9m31s
Independent Staging Quality Gate / publish (push) Has been skipped

Engine by_time ledgers now include the full candidate set so posterior columns can look up fit and windows. Delivery turns keep up to three sentences after idle persist; the card subtracts repeated traits and folds the board chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 18:32:03 +08:00
co-authored by Cursor
parent ca921c3f2a
commit 5c476ba784
28 changed files with 685 additions and 109 deletions
@@ -29,6 +29,7 @@ export type VedicChartModel = NorthIndianChartModel;
interface VedicChartSvgProps {
chart: NorthIndianChartModel;
ariaLabel: string;
highlightHouseNumbers?: ReadonlySet<number>;
}
function buildRetrogradeSet(chart: NorthIndianChartModel): Set<string> {
@@ -82,7 +83,7 @@ function OccupantStack({
);
}
export function VedicChartSvg({ chart, ariaLabel }: VedicChartSvgProps) {
export function VedicChartSvg({ chart, ariaLabel, highlightHouseNumbers }: VedicChartSvgProps) {
const chartId = useId().replace(/:/g, "");
const linesByHouse = new Map<number, string[]>();
for (const house of chart.houses) {
@@ -117,7 +118,7 @@ export function VedicChartSvg({ chart, ariaLabel }: VedicChartSvgProps) {
<g key={houseNumber}>
<polygon
points={polygonPoints(polygon)}
className="personal-report-chart-cell"
className={`personal-report-chart-cell${highlightHouseNumbers?.has(houseNumber) ? " is-changed" : ""}`}
/>
{ordinal !== null && (
<text
@@ -4,6 +4,7 @@ import { X } from "lucide-react";
import { useEffect, useRef } from "react";
import {
groupWindowTransitions,
houseTableToNorthIndianChart,
rectificationBoardPeekCopy,
workingRectificationTime,
type RectificationBoardDiff,
@@ -18,6 +19,7 @@ import {
type WindowScanLayer,
} from "@/lib/rectification-agentic/v9/refinement-packet";
import { rectificationBoardEmptyCopy } from "@/lib/rectification-surface-state";
import { VedicChartSvg } from "@/components/personal-report/vedic-chart-svg";
import { RectificationHouseTableView } from "./rectification-house-table";
import { ConfirmationGateDisclosure, TechniqueAuditDisclosure } from "./technique-audit-disclosure";
@@ -100,11 +102,24 @@ function RectificationBoardBody({
)}
{table && (
<div className="rectification-snapshot">
<RectificationHouseTableView
table={table}
changedHouses={changedHouses}
lagnaChanged={diff.lagnaChanged}
/>
<figure className="rectification-board__chart">
<VedicChartSvg
chart={houseTableToNorthIndianChart(table)}
ariaLabel={`当前本命盘,上升${table.lagna}`}
highlightHouseNumbers={new Set([
...changedHouses,
...(diff.lagnaChanged ? [1] : []),
])}
/>
</figure>
<details className="technique-audit">
<summary></summary>
<RectificationHouseTableView
table={table}
changedHouses={changedHouses}
lagnaChanged={diff.lagnaChanged}
/>
</details>
{result && <ConfirmationGateDisclosure gate={result.confirmationGate} />}
{savedStatus === "accepted" && result && result.techniqueAudit.length > 0 && (
<TechniqueAuditDisclosure rows={result.techniqueAudit} />
@@ -112,8 +127,10 @@ function RectificationBoardBody({
</div>
)}
{(scoringMinutes.length > 0 || displayMinutes.length > 0) && (
<section className="rectification-board__section" aria-label="换升时刻">
<h3></h3>
<details className="technique-audit" aria-label="换升时刻">
<summary>
· {grouped.length}
</summary>
{scoringMinutes.length > 0 && (
<ol className="rectification-board__minutes">
{scoringMinutes.map((row) => {
@@ -157,7 +174,7 @@ function RectificationBoardBody({
</ol>
</details>
)}
</section>
</details>
)}
{contrast && (
<details className="technique-audit">
@@ -5,13 +5,14 @@ import {
RECTIFICATION_USER_COPY,
REPRESENTATIVE_MINUTE_DISCLAIMER,
rangeDeliveryEventCopy,
rangeDeliveryFitCopy,
rangeDeliveryWindowCopy,
rangeDeliveryWorstCopy,
} from "@/lib/rectification-agentic/user-copy";
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
import { RectificationVerificationReport } from "@/components/rectification-verification-report";
function columnHasTraits(column: RangeDeliveryProjection["columns"][number]): boolean {
return Boolean(column.traits.d9 || column.traits.d10 || column.traits.nakshatra.length > 0);
}
export function RectificationRangeDelivery({
result,
acceptingCandidateId,
@@ -36,18 +37,25 @@ export function RectificationRangeDelivery({
: RECTIFICATION_USER_COPY.rangeDeliveryTitle;
const markdown = delivery?.verification_markdown
?? result.verificationReportMarkdown;
const sharedTraits = delivery?.shared_traits ?? [];
return (
<section className="rectification-candidates rectification-range-delivery" aria-label="生时校正区间">
<div className="rectification-candidates-heading">
<strong>{title}</strong>
</div>
{sharedTraits.length > 0 ? (
<ul className="rectification-range-delivery__shared">
{sharedTraits.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
) : null}
<ul className="rectification-range-delivery__columns">
{columns.map((column) => {
const adopted = selected === column.time;
const adopting = acceptingCandidateId === column.candidate_id;
const worst = rangeDeliveryWorstCopy(column.fit.worst);
const windows = column.windows.slice(0, 2);
const missing = column.fit == null || column.windows == null;
return (
<li
key={column.candidate_id}
@@ -59,27 +67,25 @@ export function RectificationRangeDelivery({
{" "}
{column.probability_percent}%
</p>
<div className="rectification-range-delivery__block">
<h3>{RECTIFICATION_USER_COPY.rangeDeliveryTraitsHeading}</h3>
{column.traits.d9 ? <p>{column.traits.d9}</p> : null}
{column.traits.d10 ? <p>{column.traits.d10}</p> : null}
{column.traits.nakshatra.map((line) => (
<p key={line}>{line}</p>
))}
</div>
<div className="rectification-range-delivery__block">
<h3>{RECTIFICATION_USER_COPY.rangeDeliveryFitHeading}</h3>
<p>{rangeDeliveryFitCopy(column.fit)}</p>
{worst ? <p>{worst}</p> : null}
</div>
<div className="rectification-range-delivery__block">
<h3>{RECTIFICATION_USER_COPY.rangeDeliveryWindowsHeading}</h3>
{windows.length > 0
? windows.map((window) => (
<p key={`${window.domain}-${window.from}`}>{rangeDeliveryWindowCopy(window)}</p>
))
: <p>{RECTIFICATION_USER_COPY.rangeDeliveryNoWindow}</p>}
</div>
{columnHasTraits(column) ? (
<div className="rectification-range-delivery__block">
{column.traits.d9 ? <p>{column.traits.d9}</p> : null}
{column.traits.d10 ? <p>{column.traits.d10}</p> : null}
{column.traits.nakshatra.map((line) => (
<p key={line}>{line}</p>
))}
</div>
) : null}
{missing ? (
<p className="rectification-range-delivery__muted">
{RECTIFICATION_USER_COPY.rangeDeliveryNotCompared}
</p>
) : (
<>
<p>{column.fit_line}</p>
<p>{column.window_line}</p>
</>
)}
<button
type="button"
className="rectification-range-delivery__accept"